LeetCode連結串列專題

fishers發表於2020-05-16

連結串列

套路總結

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;
    }
};

相關文章