链表的基础概念
链表通过指针将每个节点串联起来,每个节点由数据域和指针域构成。
链表可以分为:单链表,双链表(两个指针域,向前向后),循环链表等。
链表在内存中是分散的,而不像数组在内存中连续。
链表定义:
1 | struct ListNode{ |
链表操作 - 删除:
如图所示,删除节点D,只需要更改前一个节点C的next,然后将D释放即可(在java或python中不用自己释放,有GC机制)。
有一个特殊情况是删除头节点,针对这个情况可以将head指针后移,以作特殊处理;还可以创建一个虚拟头节点(dummy node)来统一删除操作。见leetcode 203. 移除链表元素。
链表操作 - 添加:
如图所示,添加节点F,需要先将F的next指向D,然后再更改C的next指向F,时间代价为O(1). 但是在找到添加节点位置需要O(1)的查找时间。
练习链表的各种操作,可以去做leetcode 707,很基础但是可以巩固其中的细节。
双指针解链表相关类型问题:
反转链表(206,24):pre,curr指针,每次将curr->next指向pre,将pre赋值为curr。24题注意next节点要考虑是否为空,这点可以放在循环里面来写。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// leetcode 24
ListNode* swapPairs(ListNode* head) {
ListNode *dummy = new ListNode(0,head);
ListNode *pre = dummy;
ListNode *curr = head;
ListNode *ne;
while(curr != nullptr && curr->next != nullptr){
ne = curr->next;
pre->next = ne;
curr->next = ne->next;
ne->next = curr;
pre = curr;
curr = curr->next;
}
return dummy->next;
}删除链表倒数第N个节点(leetcode 19): 没什么说的,快慢指针。
链表相交:相比与找倒数第N个点更难一些,关键就难在找到哪里是快慢指针初始的位置。可以遍历链表得到长度差,将长的链表的头节点先往前移动这个长度差,再两个链表指针一起走,判断指针值是否相等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33// leetcode 面试题02.07 链表相交
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
// 得到A,B链表的长度
int len_A = 0, len_B = 0;
ListNode *curr_A = headA;
ListNode *curr_B = headB;
while(curr_A != nullptr){
curr_A = curr_A->next;
++len_A;
}
while(curr_B != nullptr){
curr_B = curr_B->next;
++len_B;
}
// 将A置为更长的链表
if(len_B > len_A){
swap(len_B, len_A);
swap(headA, headB);
}
curr_A = headA;
curr_B = headB;
// 让A先走步差
for(int i=0; i<len_A - len_B; ++i){
curr_A = curr_A->next;
}
// 再一起走,判断是否有相同节点
while(curr_A != nullptr && curr_B != nullptr){
if(curr_A == curr_B) return curr_A;
curr_A = curr_A->next;
curr_B = curr_B->next;
}
return nullptr;
}环形链表:fast一次走两步,slow一次走一步。可以根据数学公式推导,在第一相遇的地方确定new fast,然后将new slow置于起点,两者每次走一步,再次相交的地方就是环起点。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// leetcode 142 环形链表
ListNode *detectCycle(ListNode *head) {
ListNode *slow = head;
ListNode *fast = head;
do{
if(fast == nullptr || fast->next == nullptr) return nullptr;
fast = fast->next->next;
slow = slow->next;
}while(fast!=slow);
slow = head;
while(fast != nullptr){
if(fast == slow){
break;
}
fast = fast->next;
slow = slow->next;
}
return fast;
}
小结
使用快慢指针可以考虑的几个维度:
- 快慢指针的初始位置如何确定?
- 快慢指针的速率:是齐头并进,还是不同速率?
链表的主要类型差不多是这些,后续有其他类型会进行补充。