引言
Linked List 是比较常见的数据结构,以下我们会介绍 Linked List 题目中常用的双指针(Two Pointers)和递归(Recursion)方法,可以用于解决一系列常见的题目。
Linked List vs. Array
Array 一组内存里连续的数据
- 能用 index 访问 –> $O(1)$ Access
- 添加元素直接添加在最后 –> Amortized $O(1)$ Add(考虑到扩容)
- 删除元素需要挪动后面所有元素位置 –> $O(n)$ Delete
Linked List 内存里不一定连续的数据
- 不能用 index 访问 –> $O(n)$ Access
- 添加元素添加在最后 –> $O(n)$ Add 双链表 $O(1)$
- 删除元素需要找到元素位置 –> $O(n)$ Delete
class ListNode { |
Two Pointers
- 两个指针指向 Linked List 节点,不再是 index
- 两个指针必定同向而行
Two Pointers 方法大概能解决 60% 的 Linked List 问题,对于每道题只需要记住定义这两点:
- 一个快一个慢,距离隔开多少
- 两个指针移动速度
Linked List 找中间节点
两个指针同向而行,一个每次前进 2 个节点,另一个每次前进 1 个节点,当快的指针到最后,慢的指针就停留在了中点。
对于第一个偶数链表来说中间节点是 2 或 3 都可以,对于第二个奇数链表中间节点就是最中间的 3。
High Level Idea:
- Initialize 2 pointers i and j pointing to the head
- While the next move of the faster pointer j is valid (inside bound), move j two steps and i one step forward
- Return the node at i
ListNode linkedlistMiddleNode(ListNode head) { |
Linked List 找倒数第k个节点
两个指针先隔开 k 个位置,然后每次相同速度向前直到快指针出界,慢指针就停留在倒数第 k 个节点。
High Level Idea:
- Initialize 2 pointers i and j pointing to the head
- Move j k step forward
- Move both i and j one step forward at a time until j is out of bound
ListNode linkedlistLastKNode(ListNode head, int k) { |
Recursion
面试时可能会刻意考察 recursion 是否理解,尤其是简单的题目。
Bottom up recursion 3 steps:
- Asek for subproblem result
- Do something in current level of recursion
- Return result
Recursion 最重要的一点:相信自己的 recursion function 是对的。
Reverse Linked List
这里举个简单的例子,倒置链表。注意倒置不仅仅是数值的变化,还有指针引用的改变。
我们假设 3 后面已经 Reversed 好了,不会管他里面什么样,相当于一个黑盒不需要知道里面的内容。对于 3 这一层来说首先要把 3.next
的指针返回到 3:
3.next.next = 3; |
然后需要把 3 的 next
指针收回:
3.next = null; |
对于每一层的 recursion:
- 问下一次要结果
- Reverse 当前所在的 ListNode
- 返回 reversed head
High Level Idea:
- Ask for next recursion level result
- Reverse current node
- Return reversed head
ListNode reverse(ListNode head) { |
总结
大家可以通过更多类似题目进行练习:
- Delete Node In a Linked List (237)
- Linked List Cycle (141)
- Reverse Linked List II (92)
- Reverse Nodes In k-Group (25)