之前刷leetcode,很多链表的题目刷起来很慢,很费劲,说到底,对链表这个数据结构有种不信任感,因为每次经过一个节点之后就没有办法返回了(单项链表),平时用习惯了数组,对这种变化有点不适应。
这里说一说我在刷题过程中解决链表相关问题所用的技巧,以及如何更好的使用链表
一、dummyHead
leetcode里面传进来的head是带有数据的,这个没有任何节点指向head,因此对head的修改有时会比较的麻烦,因此可以先建立一个假想节点指向head,以后需要返回head时,直接返回:dummyHead->next即可。
ListNode* dummyhead = &ListNode(-1);
这个技巧应该是最常用的了。
二、断链
这个操作通过函数cut(l, n)实现,其作用是:将链表 l 切掉前 n 个节点,并返回后半部分的链表头。
// 将链表head的前n个节点取下来,并返回后续链表的头结点
ListNode* cut(ListNode* head, int n) {
ListNode *p = head;
while (p != NULL && --n)
p = p->next;
if (p == NULL)
return NULL;
ListNode *q = p->next;
p->next = NULL;
return q;
}
这个操作很重要,因为实际上,当题目给我们一个链表头,告诉我们需要对该链表做什么操作时,我们并不知道head的前面有什么,也没有办法知道,因为给的链表是单向的。
而这个断链操作可以减小问题的规模,假设链表长度为n,每次断链之后得到的链表长度为m,相比于原来的问题规模缩小了很多,这意味着许多问题都可以用分治法来做了。
当问题规模减小到一定程度的时候,就可以直接解决了,我们通过合并每个解决了的子问题来得到原问题的解。
三、合并
之前说过使用分治法,其中关键的一步:合并每个子问题的解。
一般链表的合并方法是首尾相接,这个没什么好说的,这里说一说有序链表的合并
// 将两个有序链表合并为一个大的有序链表
ListNode* merge(ListNode* list1, ListNode* list2) {
ListNode dummyHead(-1);
ListNode *p = &dummyHead;
while (list1 != NULL && list2 != NULL) {
if (list1->val > list2->val) {
p->next = list2;
list2 = list2->next;
p = p->next;
}
else {
p->next = list1;
list1 = list1->next;
p = p->next;
}
}
if (list1)
p->next = list1;
if (list2)
p->next = list2;
return dummyHead.next;
}
四、快慢指针
什么是快慢指针?
就是定义两个指针,一个指针(慢指针)的移动速度为v,另一个指针(快指针)速度为2v,如此一来,经过相同的时间,快指针走过的路程是慢指针的2倍。
C++代码:
ListNode *fast = head, *slow = head;
while(fast && fast->next){
fast = fast->next->next;
slow = slow->next;
}