連結串列
套路總結
1.多個指標 移動
2.虛假連結串列頭:凡是有可能刪除頭節點的都建立一個虛擬頭節點,程式碼可以少一些判斷(需要用到首部前一個元素的時候就加虛擬頭指標)
3.快慢指標
如leetcode160 快慢指標找連結串列環的起點
19. 刪除連結串列的倒數第N個節點
題目要求:只掃描一遍
刪除連結串列,肯定要找到被刪節點的前一個節點
1.找到倒數第n個節點的前一個節點(倒數第n+1)
2.雙指標
first指標指向第k個,second頭指標指向虛假頭節點,兩個指標一起移動,當first指標指向最後一個節點的時候(first下一個節點為NULL),就說明second到達了倒數第k個節點
3.刪除即可 second ->next = second->next->next
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
auto dummy = new ListNode(-1);
dummy->next = head;
auto first = dummy;
auto second = dummy;
while(n--) first = first->next;
while(first->next != NULL){
second = second->next;
first = first->next;
}
second->next = second->next->next;
return dummy->next;
}
};
237. 刪除連結串列中的節點
例如,給定node指向5這個點,刪除5這個點
真正意義刪除要知道被刪除節點的上一個點
假裝刪除,把這個點的值偽裝成下一個點的值,把下一個點刪掉即可
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
void deleteNode(ListNode* node) {
if(node->next){
node->val = node->next->val;
node->next = node->next->next;
}
return;
}
};
C++語法把node兩個屬性的值都一起替換為下一個節點的屬性
*(node) = *(node->next);
83. 刪除排序連結串列中的重複元素
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
auto *first = head;
while(first && first->next){
if(first->val == first->next->val){
first->next = first->next->next;
}else{
first = first->next;
//這裡first可能移動到了空 所以要判斷first是否空
}
}
return head;
}
};
82. 刪除排序連結串列中的重複元素 II
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
auto dummy = new ListNode(-1);
dummy->next = head;
auto pre = dummy,cur = pre->next;
int cnt = 0;
while(pre && cur){
cnt = 0;
auto nxt = cur->next;
while(nxt && nxt->val == cur->val) {
cnt++;
nxt = nxt->next;
}
if(cnt >= 1){
pre->next = nxt;
cur = pre->next;
}else{
pre = pre->next;
cur = pre->next;
}
}
return dummy->next;
}
};
61. 旋轉連結串列
兩個指標,距離為k
(不需要用到虛擬頭節點,頭節點會改變時用到)
之後讓first->next指向開頭head,再讓head指向現在的頭(second->next)!
再讓second->next指向空
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
if(!head) return NULL;
int n = 0;
for(auto p = head;p;p=p->next) n++;
k %= n;
auto first = head,second = head;
while(k--) first = first->next;
while(first->next){
first=first->next;
second=second->next;
}
first->next = head;
head = second->next;
second->next = NULL;
return head;
}
};
24. 兩兩交換連結串列中的節點
1.建立虛擬頭節點,因為頭節點可能會改變
2.三個指標
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
auto dummy = new ListNode(-1);
dummy->next = head;
for(auto p = dummy;p->next && p->next->next;){
auto a = p->next,b = a->next;
p->next = b;
a->next = b->next;
b->next = a;
p = a; //指向下一個新的兩對前的最後一個點
}
return dummy->next;
}
};
206. 反轉連結串列
兩個翻轉指標a,b;一個保留指標c保留b後面的鏈防止被刪除,不需要虛擬頭節點因為不需要用到首部前一個
分三步
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(!head) return NULL;
auto a = head,b = head->next;
while(b){
auto c = b->next;
b->next = a;
a = b;
b = c;
}
head->next = NULL;//原來頭是原來的第一節點 現在的最後一個節點所以指向空
head = a;
return head;
}
};
92. 反轉連結串列 II
1.因為頭節點會發生變化,設定虛擬頭節點
2.a指標移動到翻轉前一個點,b指標移動第一個翻轉的點,d指標移動到最後一個翻轉的點。c指標指向最後一個翻轉的點的下一個點。然後翻轉b~d之間的點和206題一樣
3.連線a->d,b->c
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int m, int n) {
if(m == n) return head;
auto dummy = new ListNode(-1); //虛擬頭節點
dummy->next = head;
//找到a和d
auto a = dummy,d = dummy;
for(int i=0;i<m-1;i++) {
a = a->next;//不設定虛擬頭節點的話,如果n=1就找不到了a
}
for(int i=0;i<n;i++) d = d->next;
//找到b和c
auto b = a->next, c = d->next;
//翻轉b和d之間的數字
for(auto first = b->next,second = b; first != c;){
auto third = first->next;
first->next = second;
second = first,first = third;
}
//連線
b->next = c;
a->next = d;
return dummy->next;
}
};
160. 相交連結串列
相遇:當指標p和指標q走的路程相等時相遇
考慮都走a+b+c的倍數,肯定會相遇
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
auto tempHeadA = headA;
auto tempHeadB = headB;
while(tempHeadA != tempHeadB){
if(tempHeadA) tempHeadA = tempHeadA->next;
else tempHeadA = headB;
if(tempHeadB) tempHeadB = tempHeadB->next;
else tempHeadB = headA;
}
return tempHeadB;
}
};
142. 環形連結串列 II
快慢指標
1.快指標慢指標從head頭部出發,fast快指標每次走兩步,slow慢指標每次走一步直到相遇。
2.把其中一個指標移動到head頭部,快慢指標再每次走一步直到相遇,相遇點即為答案;
證明:利用快指標走動過的是慢指標的二倍,假設環起點座標為x,第一次相遇點距離換起點距離為y。
可列公式2×(x+n1×c+y)=x+y+n2×c ,化簡得x+y=(n2-n1)×c。
大白話說就是:非環部分的長度+環起點到相遇點之間的長度就是環的整數倍。
即x+y為環的整數倍
那麼第一次相遇時我們現在距離環起點為y,所以只要再走x就到環起點了
再走x的話就讓一個指標從head走,另一個從第一次相遇點走,每次都走1步
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
auto fast = head,slow = head;
while(fast && fast->next){
fast = fast->next;
fast = fast->next; //快指標移動兩次
slow = slow->next; //慢指標移動1次
if(fast == slow){ //當快慢指標相遇時退出
break;
}
}
if(fast==NULL || fast->next == NULL)
return NULL;
else{
slow = head; //讓其中一個指標移動到頭部
while(fast != slow){ //再走到相遇點即可
fast = fast->next;
slow = slow->next;
}
return slow;
}
}
};
148. 排序連結串列
要求空間常數,時間O(nlogn)
因為快排用到遞迴(棧),空間為logn;遞迴版歸併空間消耗大;所以用迭代版歸併
自底向上程式碼寫法:先列舉長度為2,分成一半,左右歸併;再列舉長度為4...
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* sortList(ListNode* head) {
int n = 0;
for(auto p = head; p ; p = p -> next) n++;
auto dummy = new ListNode(-1);
dummy->next = head;
for(int i=1; i<n ; i*=2){ //列舉每一段的一半長
auto cur = dummy;
for(int j=0; j+i<n ; j+=i*2){
auto left = cur->next; //左半段邊界指標
auto right = cur->next; //右半段邊界指標
for(int k=0;k<i;k++) right = right->next;
int l = 0,r = 0;
while(l < i && r < i && right){ //歸併比較左右哪個大
if(left->val <= right-> val){
cur->next = left;
cur = left;
left = left->next;
l++;
}else{
cur->next = right;
cur = right;
right = right->next;
r++;
}
}
//一個先到了末尾 所以要拼接另一端的剩餘部分
while(l < i){
cur->next = left;
cur = left;
left = left->next;
l++;
}
while(r < i && right){
cur->next = right;
cur = right;
right = right->next;
r++;
}
cur->next = right; //拼接下一段 這裡的right最終指向了下一段的left
}
}
return dummy->next;
}
};
21. 合併兩個有序連結串列
(線性合併) O(n)O(n)
1.新建頭部的保護結點 dummy,設定 cur 指標指向 dummy。
2.如果p的值比q小,就將cur->next = p,否則讓cur -> next = q (選小的先連線)
迴圈以上步驟直到 l1l1 或 l2l2 為空。
3.將剩餘的 p或 q連 接到 cur 指標後邊。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
auto dummmy = new ListNode(-1);
auto cur = dummmy;
auto p = l1,q = l2;
//選小的優先
while(p && q){
if(p->val <= q->val){
cur->next = p;
cur = p;
p = p->next;
}else{
cur->next = q;
cur = q;
q = q->next;
}
}
//加入剩餘
while(p){
cur->next = p;
p = p->next;
}
while(q){
cur->next = q;
q = q->next;
}
// cur->next = (p != NULL ? p : q);
return dummmy->next;
}
};