第一天刷题。一个平实的开始,希望能坚持下来,不求波涛汹涌,大浪淘沙,但求静水流深,川流不息。
先学习双指针。题目方向分为两个:链表和数组。
在处理数组和链表相关问题时,双指针技巧是经常用到的,双指针技巧主要分为两类:左右指针和快慢指针。
所谓左右指针,就是两个指针相向而行或者相背而行;而所谓快慢指针,就是两个指针同向而行,一快一慢。
链表中的双指针
第一道:21.合并两个有序链表
思路一:申请第三个链表的空间,拷贝两个链表中较小的一个。
代码v1.0:
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 34 35 36 37 38 39 40 41 42 43
| class Solution { public: bool comp(ListNode* l1, ListNode* l2){ if(l1->val > l2->val) return true; else return false; } bool Empty_Judge(ListNode* l){ if(l->val==0) return true; else return false; } ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) { if(Empty_Judge(list1)) return list2; if(Empty_Judge(list2)) return list1; ListNode* list ; ListNode* head = list; while(list1!=nullptr&&list2!=nullptr) { list = new ListNode; if(comp(list1,list2)){ list->val = list2->val; list = list->next; list2 = list2->next; } else{ list->val = list1->val; list = list->next; list1 = list1->next; } } while(list1!=nullptr){ list = new ListNode(list1->val); list=list->next; list1=list1->next; } while(list2!=nullptr){ list = new ListNode(list2->val); list=list->next; list2=list2->next; } return head; }
};
|

问题:返回的 head 没有任何数值。
解决:
代码V2.0:
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 34 35 36 37 38 39 40
| class Solution { public: ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) { if(!list1) return list2; if(!list2) return list1; ListNode* list = new ListNode ; ListNode* head = list; while(list1&&list2) { if(list1->val > list2->val){ list->val = list2->val; list2 = list2->next; } else{ list->val = list1->val; list1 = list1->next; } list->next = new ListNode; list = list->next; }
while(list1) { list ->val = list1 -> val; if(list1->next) list->next = new ListNode; list=list->next; list1=list1->next; } while(list2) { list ->val = list2 -> val; if(list2->next) list->next = new ListNode; list=list->next; list2=list2->next; }
return head; }
};
|
规避了野指针问题和相对的代码冗余问题。
思路二:在两个指针现有的空间上,只做穿针引线的工作(连接起来)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) { if(!list1) return list2; if(!list2) return list1; ListNode* list = new ListNode; ListNode* head = list; while(list1&&list2) { if(list1->val > list2->val){ list -> next = list2; list = list2; list2 = list2->next; } else{ list -> next = list1; list = list1; list1 = list1->next; } } list -> next = list1 ? list1 : list2; return head -> next; }
|
问题:
- 开始的判断是否为空可以省略。
- while 中出现重复点(只是需要一点点直觉)
解决:
看思路三。
**思路三:**labuladong ,虚拟头结点值得注意。整体与思路二没什么区别。
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
| ListNode mergeTwoLists(ListNode l1, ListNode l2) { ListNode dummy = new ListNode(-1), p = dummy; ListNode p1 = l1, p2 = l2; while (p1 != null && p2 != null) { if (p1.val > p2.val) { p.next = p2; p2 = p2.next; } else { p.next = p1; p1 = p1.next; } p = p.next; }
if (p1 != null) { p.next = p1; }
if (p2 != null) { p.next = p2; }
return dummy.next;
|
}
代码中还用到一个链表的算法题中是很常见的「虚拟头结点」技巧,也就是 dummy 节点。你可以试试,如果不使用 dummy 虚拟节点,代码会复杂很多,而有了 dummy 节点这个占位符,可以避免处理空指针的情况,降低代码的复杂性。
拓展:
思路四:递归
1 2 3 4 5 6 7 8 9 10 11 12 13
| struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2) { if(l1==NULL) return l2; if(l2==NULL) return l1; if(l1->val < l2->val){ l1->next = mergeTwoLists(l1->next,l2); return l1; }else{ l2->next = mergeTwoLists(l1,l2->next); return l2; } }
|
类似汉诺塔的思路。
第二道:分割链表
V1.0
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 34 35 36
| class Solution { public: ListNode* partition(ListNode* head, int x) { ListNode* tail = head; while(tail->val < x ) tail=tail->next; ListNode* temp = tail; ListNode* maxdot = tail; while(head->val > x) head = head->next; tail = head;
while(temp) { if(temp->val < x){ tail ->next = temp; tail = tail->next; } if(temp->val >x &&temp->next->val < x ){ tail->next = temp->next; if(temp->next->next) temp->next = temp->next->next; else { tail->next = temp->next; temp->next = nullptr; break; } } temp = temp->next; } tail->next = maxdot; return head; } };
|
现在思路也不是很清晰

思路二:双头结点
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
| ListNode partition(ListNode head, int x) { ListNode* dummy1 = new ListNode(-1); ListNode* dummy2 = new ListNode(-1); ListNode* p1 = dummy1, p2 = dummy2; ListNode* p = head; while (p != null) { if (p.val >= x) { p2.next = p; p2 = p2.next; } else { p1.next = p; p1 = p1.next; } ListNode temp = p.next; p.next = null; p = temp; } p1.next = dummy2.next;
return dummy1.next; }
|
创建两个头结点,先断开,后连接。
思路三:递归
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
| class Solution { public: ListNode* small = nullptr; pair<ListNode*, ListNode*> helper(ListNode* head, int x) { if (head == nullptr) return make_pair(nullptr, nullptr); if (head->val < x) small = head; pair<ListNode*, ListNode*> next = helper(head->next, x); if (head->val < x) { head->next = next.first; next.first = head; } else { head->next = next.second; next.second = head; } return next; } ListNode* partition(ListNode* head, int x) { pair<ListNode*, ListNode*> nodes = helper(head, x); if (nodes.first) { small->next = nodes.second; return nodes.first; } else { return nodes.second; } } };
|
大多数链表题递归的时候可以无脑写一句,然后只要让思路从后向前进行就可以了。
1
| cppListNode* node = func(head->next);
|
这里返回值选择了两个pair,first保存小于x的结点,second保存大于x的结点。
思路四:原地连接
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 34 35 36 37 38 39 40
| struct ListNode* partition(struct ListNode* head, int x){ if(!head || !head->next){ return head; } struct ListNode *p = head, *q = head, *temp = NULL; if(p->val < x){ while(p->next->val < x){ p = p->next; if(p->next == NULL){ return head; } } q = p->next; } else{ p = NULL; } while(q->next){ if(q->next->val < x){ temp = q->next; q->next = temp->next; if(p == NULL){ temp->next = head; head = temp; p = head; } else{ temp->next = p->next; p->next = temp; p = temp; } } else{ q = q->next; } } return head; }
|
p指针指向小于 x 的值,p == null 就连接第一个大于x的值,p!=null 就按顺序进行尾插法;
(只是 p 的”尾”是 q 的”头”)
q指针指向大于 x 的值,初始是第一个大于x的值, 尾插法依次往后放;
temp 服务于p的尾插法,作为中间变量。
(q 尾插循环的前面,是p,q 的初始化工作,分别指定小于x值的尾端 和 大于x值的尾端)
第三道:合并K个升序链表
思路一:遍历合并两个链表到第一个链表中去
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Solution { public: ListNode* merge(ListNode* list1,ListNode* list2) {...} ListNode* mergeKLists(vector<ListNode*>& lists) { if(lists.empty()) return nullptr; vector<ListNode*>::iterator it = lists.begin(); vector<ListNode*>::iterator next = lists.begin()+1; for(;next!=lists.end();next++){ *it = merge(*it,*next); } return *it; } };
|
**思路二:**labladong :配合优先队列的二叉堆,获取最小k结点
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
| ListNode mergeKLists(ListNode[] lists) { if (lists.length == 0) return null; ListNode* dummy = new ListNode(-1); ListNode* p = dummy; PriorityQueue<ListNode*> pq = new PriorityQueue<>( lists.length, (a, b)->(a.val - b.val)); for (ListNode head : lists) { if (head != null) pq.add(head); }
while (!pq.isEmpty()) { ListNode node = pq.poll(); p.next = node; if (node.next != null) { pq.add(node.next); } p = p.next; } return dummy.next; }
|
思路三:分治递归
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 34 35 36 37 38 39 40 41
| class Solution {
public ListNode mergeKLists(ListNode[] lists){ if(lists.length == 0) return null; if(lists.length == 1) return lists[0]; if(lists.length == 2){ return mergeTwoLists(lists[0],lists[1]); }
int mid = lists.length/2; ListNode[] l1 = new ListNode[mid]; for(int i = 0; i < mid; i++){ l1[i] = lists[i]; }
ListNode[] l2 = new ListNode[lists.length-mid]; for(int i = mid,j=0; i < lists.length; i++,j++){ l2[j] = lists[i]; }
return mergeTwoLists(mergeKLists(l1),mergeKLists(l2));
} public ListNode mergeTwoLists(ListNode l1, ListNode l2) { if (l1 == null) return l2; if (l2 == null) return l1;
ListNode head = null; if (l1.val <= l2.val){ head = l1; head.next = mergeTwoLists(l1.next, l2); } else { head = l2; head.next = mergeTwoLists(l1, l2.next); } return head; } }
|
思路四:STL中的优先队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Solution { public: ListNode* mergeKLists(vector<ListNode*>& lists) { auto head = ListNode(0); auto comp = [](ListNode* const &a, ListNode* const &b){return a->val > b->val;}; priority_queue<ListNode*, vector<ListNode*>, decltype(comp)> q(comp); for (auto &h : lists) if (h != nullptr) q.push(h); auto p = &head; while (!q.empty()) { p->next = q.top(); p = p->next; q.pop(); if (p->next != nullptr) q.push(p->next); } return head.next; } };
|
第四道:单链表的倒数第 k 个节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| ListNode* removeNthFromEnd(ListNode* head, int n) { if(n==0||!head) return head; if(!head->next) return NULL; ListNode* fformer = head; ListNode* former = head; ListNode* latter = head; while(--n>0) latter = latter->next; while(latter->next) { if(former!=head) fformer = fformer->next; latter = latter -> next; former = former -> next; } if(former == fformer) head = former->next; else fformer->next = former->next; return head;
|
第五道:链表的中间结点
1 2 3 4 5 6 7 8 9 10 11
| ListNode* middleNode(ListNode* head) { ListNode* fast = head; ListNode* slow = head; while(fast->next) { fast = fast->next; if(fast->next) fast=fast->next; slow = slow->next; } return slow; }
|
第六道:相交链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA == null || headB == null) return null; ListNode pA = headA, pB = headB; while(pA != pB) { pA = pA == null ? headB : pA.next; pB = pB == null ? headA : pB.next; } return pA; }
|
这个赋值和 ?条件判断 的配合非常有趣。
简短的 if 语句可用 ?来代替。
思路二:
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
| public ListNode getIntersectionNode(ListNode headA, ListNode headB) { int lenA = 0, lenB = 0; for (ListNode p1 = headA; p1 != null; p1 = p1.next) { lenA++; } for (ListNode p2 = headB; p2 != null; p2 = p2.next) { lenB++; } ListNode p1 = headA, p2 = headB; if (lenA > lenB) { for (int i = 0; i < lenA - lenB; i++) { p1 = p1.next; } } else { for (int i = 0; i < lenB - lenA; i++) { p2 = p2.next; } } while (p1 != p2) { p1 = p1.next; p2 = p2.next; } return p1; }
|
第七道:判断是否为环形链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| boolean hasCycle(ListNode head) { ListNode slow = head, fast = head; while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; if (slow == fast) { return true; } } return false; }
|
不解释
第八道:找到环形链表的起点
流程起始同上,先让快慢指针相遇。
设相遇点距离头结点为 k ,即慢指针的路程。快指针速度是慢指针二倍,路程即 k+k=2k
设环形起点距离相遇点为 m , 则慢指针距离起点 k-m。巧合的是,相遇点指针不断向后走,距离起点同样为k-m 。
所以让其中任意一个指针回到起点,经过k-m位移,另一个指针就会到达环形链表起点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| ListNode detectCycle(ListNode head) { ListNode fast, slow; fast = slow = head; while (fast != null && fast.next != null) { fast = fast.next.next; slow = slow.next; if (fast == slow) break; } if (fast == null || fast.next == null) { return null; }
slow = head; while (slow != fast) { fast = fast.next; slow = slow.next; } return slow; }
|
数组中的双指针
在数组中并没有真正意义上的指针,但我们可以把索引当做数组中的指针,这样也可以在数组中施展双指针技巧。
基本的快慢双指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| int removeDuplicates(int[] nums) { if (nums.length == 0) { return 0; } int slow = 0, fast = 0; while (fast < nums.length) { if (nums[fast] != nums[slow]) { slow++; nums[slow] = nums[fast]; } fast++; } return slow + 1; }
|
大同小异的一个链表题:
删除排序链表中的重复元素](https://leetcode.cn/problems/remove-duplicates-from-sorted-list/)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| struct ListNode* deleteDuplicates(struct ListNode* head){ typedef struct ListNode ListNode; if(!head) return head; ListNode* slow = head; ListNode* fast = head; while(fast) { if(fast->val!=slow->val) { slow=slow->next; slow->val=fast->val; } fast=fast->next; } slow->next = NULL; return head;
|
第二道:移除元素
左右指针:
1 2 3 4 5 6 7 8 9 10 11 12
| class Solution { public: int removeElement(vector<int>& nums, int val) { int j = nums.size() - 1; for (int i = 0; i <= j; i++) { if (nums[i] == val) { swap(nums[i--], nums[j--]); } } return j + 1; } };
|
快慢指针:
1 2 3 4 5 6 7 8 9 10
| while(fast<numsSize) { if(nums[fast]!=val) { nums[slow] = nums[fast]; slow++; } fast++; } return slow;
|
扩展:保留重复k个元素的基础上删除多余元素
1 2 3 4 5 6 7 8 9 10 11 12 13
| class Solution { public: int work(vector<int>& nums, int k) { int len = 0; for(auto num : nums) if(len < k || nums[len-k] != num) nums[len++] = num; return len; } int removeDuplicates(vector<int>& nums) { return work(nums, 2); } };
|
第三道:移动零
v1.0
1 2 3 4 5 6 7 8 9 10 11 12 13
| void moveZeroes(vector<int>& nums) { int fast = 0,slow=0; while(fast<nums.size()) { if(nums[fast]!=0){ nums[slow++] = nums[fast]; } fast++; } while(slow<fast){ nums[slow++]=0; } }
|
v2.0
1 2 3 4 5 6 7 8 9 10 11
| int left = 0; int right = 0; while(right < nums.size()) { if(nums[right]) { swap(nums[left], nums[right]); left++; } right++; }
|
遇到非0直接交换就可以
第四道:最长回文子串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| string longestPalindrome(string s) { string res = ""; for(int i=0;i<s.size();i++) { string s1=palindrome(s,i,i); string s2=palindrome(s,i,i+1); res = s1.size()>res.size()?s1:res; res = s2.size()>res.size()?s2:res; } return res; } string palindrome(string s,int l,int r) { while(l>=0&&r<s.size()&&s[l]==s[r]) { l--; r++; }; return s.substr(l+1,r-l-1); }
|
左右指针,向两侧扩散。
遍历中心点。
中心点分奇数、偶数两种情况,依次比较即可。