【面试准备】数据结构
算法
1. 链表 linkedlist
1.1. 翻转列表 reverse the linkedlist
ListNode* ReverseList(ListNode* pHead) {
ListNode *root = pHead;
ListNode *pre = NULL;
ListNode *next = NULL;
if(pHead==NULL) return NULL;
while(root->next){
// 先保存下一个节点
next = root->next;
// 让 “头” 的下一个节点指向前一个
root->next = pre;
// 因为要往后走,所以“之后的前一个”就是现在的 “头”
pre = root;
// “头” 去下一个节点
root = next;
}
root->next=pre;
return root;
}
1.2. 返回链表中倒数第 n 个节点 Returns the nth reciprocal node in a linked list
这么巧妙的解决办法我怎么就没想到呢?
- 直接用两个快慢指针……
- 倒数第 n 个就让一个指针先走 n 步
- 然后再让两个一起走
- 先走的那个到末尾了。那么,慢走的那个就到了倒数第 n 位。
1.3. 判断链表是否存在环 Ring Exists
居然有人说直接用快慢指针是错误答案?
- 快慢指针法 快指针 pf (pointer fast) 每次移动2个节点,慢指针 ps (pointer slowly) 每次移动1个节点,如果快指针能够追上慢指针,那就说明其中有一个环,否则不存在环。
这没啥问题吧?!!
某网站某博客的神逻辑:
想像一种情况,当快指针走到一个环的时候,慢指针还离快指针很远,甚至当快指针走出环的时候慢指针还没到达环,这时候快指针永远不会追上慢指针。所以快慢指针无法解决链表存在循环的问题,快慢指针能解决的只是链表存在环的问题,也就是这个循环在链表尾部。可以说链表存在环是链表存在循环的一种特殊情况。
您都成环了啊喂!当这是过山车吗?
- map 存储映射
使用
map
进行映射。定义map<Node*, int> m
将每个Node*
映射为数组下标,并赋值为一个int
。然后从链表的头指针开始往后遍历,每次遇到一个指针p,就判断m[p]
是否为0。如果为0,则将m[p]
赋值为1,表示该节点第一次访问;而如果m[p]
的值为1,则说明这个节点已经被访问过一次了,于是就形成了环。
1.3.1. 若单链表有环,如何找出环的入口节点。 If the single-chain table has a ring, how to find the entry node of the ring.
同上……如果用 map 来存储那么一切都很简单了……如果再次访问到 1 就说明到了。 但是如果是用快慢指针? 先得计算出环的长度 n ……(如何计算?再绕一圈然后计数)
然后,有点神奇
- 再从头开始用两个指针
- 一个先走 长度 n
- 然后两个再以相同的步幅同时走。
- 再能相遇就是对应的要求的入口节点位置。
(为什么?因为两个指针的间隔是 N,环的长度也是 N,两个能相遇的时候就是两个间隔 N 的时候)
1.4. 链表去重 remove duplicate elements
-
方法一,最简单粗暴的 (我都不想讲了……太丢人了) 知道数组怎么暴力的吧……道理一样……
- 用一个map(好了,勉强看起来不那么暴力)
- 建立一个hash表,key为链表中已经遍历的节点内容,初始时为空。
- 从头开始遍历单链表中的节点。
- 如果节点内容已经在hash表中存在,则删除此节点,继续向后遍历。
- 如果节点内容不在hash表中,则保留此节点,将节点内容添加到hash表中,继续向后遍历。
(注意删除时需要知道前一节点。)
2. 栈和队列 Stack and queue
### 两个栈实现队列 Two stack implementation queue
- 入队:直接把元素压入 stack1 中;
- 出队:
- 若 stack2 中不为空,则直接弹出 stack2 中的元素;
- 若 stack2 中为空,则将 stack1 中的所有元素倒入 stack2 中,然后弹出 stack2 的栈顶元素。
- 若两个栈都为空,则队列为空队,无法出队。
T pop() {
if (!stack2.isEmpty()) {
return stack2.pop();
} else { // if stack2 isEmpty
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
return stack2.pop();
}`
}
好像有点不能理解? 入队时非常简单。在出队时,多数情况下可以直接通过弹出 stack2 完成。如果把stack1中的元素倒入stack2中,则一般不用每次都进行这样的操作。最坏的情况就是出队一个元素、入队一个元素这样的循环,导致每次出队都需要转移元素。
思考的内容:
- 一定得是 stack2 空了才能让 1 的东西进来。(为什么呢?因为这是已有的顺序,1 只是暂存倒序,栈全部逆置了再依次出就是队列的出队顺序了)
- 同样道理,2 里面也就是已有的顺序。已有的:出队顺序。
哇,这个好鸡贼啊。