鏈表
套路總結
1.多個指針 移動
2.虛假鏈表頭:凡是有可能刪除頭節點的都創建一個虛擬頭節點,代碼可以少一些判斷(需要用到首部前一個元素的時候就加虛擬頭指針)
3.快慢指針
如leetcode142 快慢指針找鏈表環的起點
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;
}
};