leetcode-鏈表十題

總結自:https://www.bilibili.com/video/BV1jt411J7tC


19. Remove Nth Node From End of List

Given linked list: 1->2->3->4->5, and n = 2.

After removing the second node from the end, the linked list becomes 1->2->3->5.
Note:

Given n will always be valid.

Follow up:

Could you do this in one pass?


思路:

雙指針,因爲要刪除倒數第n個節點,那麼就要找到倒數第n+1個節點,所以其中一個指針先向右移動n個節點,這樣當靠右的節點到達鏈表尾的時候,另一個指針即指向倒數第n+1個節點


要點:

ListNode(int x) : val(x), next(NULL) {}	// 用來初始化
auto dummy = new ListNode(-1);	// auto會根據等號右邊的值的類型對確定變量的類型
ListNode* dummy = new ListNode(-1); // 跟上一條等價

因爲可能刪除的是頭節點,因此我們創建一個虛擬的節點指向頭節點,並且最終返回虛擬節點的下一個節點,這樣當被刪除的是頭節點時也不需要特殊判斷


/**
 * 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, second = dummy;
        while(n--) first = first->next;
        while(first->next)
        {
            first = first->next;
            second = second->next;
        }
        second->next = second->next->next;

        return dummy->next;
    }
};

237. Delete Node in a Linked List

Write a function to delete a node (except the tail) in a singly linked list, given only access to that node.

Given linked list – head = [4,5,1,9], which looks like following:

image-20200401111218974

Example 1:

Input: head = [4,5,1,9], node = 5
Output: [4,1,9]
Explanation: You are given the second node with value 5, the linked list should become 4 -> 1 -> 9 after calling your function.
Example 2:

Input: head = [4,5,1,9], node = 1
Output: [4,5,9]
Explanation: You are given the third node with value 1, the linked list should become 4 -> 5 -> 9 after calling your function.

Note:

The linked list will have at least two elements.
All of the nodes’ values will be unique.
The given node will not be the tail and it will always be a valid node of the linked list.
Do not return anything from your function.


思路:一般要刪除一個節點,我們要找到該節點的前一個節點,但這道題只給出要刪除的節點,所以我們複製該節點的後一個節點的值,然後刪除後一個節點,這樣從整個鏈表上看就好像是刪除了該節點


/**
 * 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) {
        node->val = node->next->val;
        node->next = node->next->next;
        // 上面兩句可以簡寫爲
        // *(node) = *(node->next);
    }
};

83. Remove Duplicates from Sorted List

Given a sorted linked list, delete all duplicates such that each element appear only once.

Example 1:

Input: 1->1->2
Output: 1->2
Example 2:

Input: 1->1->2->3->3
Output: 1->2->3


思路:遍歷一遍,如果該節點的值和下一個節點的值相同,就刪去下一個節點


/**
 * 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 temp = head;
        while(temp)
        {
            if(temp->next && temp->val == temp->next->val) // 注意判下個節點是否爲空
                temp-> next = temp->next->next;
            else
                // 只有當前後兩個節點值都不同時才指向下一個節點
                // 否則如果再下一個節點的值仍然跟前兩個值相等,就會出錯
                temp = temp->next; 
        }

        return head;
    }
};

61. Rotate List

Given a linked list, rotate the list to the right by k places, where k is non-negative.

Example 1:

Input: 1->2->3->4->5->NULL, k = 2
Output: 4->5->1->2->3->NULL
Explanation:
rotate 1 steps to the right: 5->1->2->3->4->NULL
rotate 2 steps to the right: 4->5->1->2->3->NULL
Example 2:

Input: 0->1->2->NULL, k = 4
Output: 2->0->1->NULL
Explanation:
rotate 1 steps to the right: 2->0->1->NULL
rotate 2 steps to the right: 1->2->0->NULL
rotate 3 steps to the right: 0->1->2->NULL
rotate 4 steps to the right: 2->0->1->NULL


思路:旋轉k次,相當於以倒數第k個節點當頭節點,鏈表尾指向原先的頭節點,因此可以用19. Remove Nth Node From End of List的雙指針方法定位兩個目標節點,由於k可能很大,因此需要先取模

/**
 * 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 left = head, right = head;
        while(k--) right = right->next;
        while(right->next)
        {
            left = left->next;
            right = right->next;
        }   

        right->next = head;	// 鏈表末尾指向原先的頭
        head = left->next;	// 新的頭節點
        left->next = NULL;	// 新的鏈表尾

        return head;
    }
};

24. Swap Nodes in Pairs

Given a linked list, swap every two adjacent nodes and return its head.

You may not modify the values in the list’s nodes, only nodes itself may be changed.

Example:

Given 1->2->3->4, you should return the list as 2->1->4->3.


思路:頭節點有變化,因此用一個虛擬節點指向頭節點,用臨時節點p遍歷鏈表,用a和b分別表示p後面兩個要交換的節點,交換結束後p指向a,繼續交換後兩個節點,如果長度爲奇數,那最後一個節點不做改動


/**
 * 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 = p->next->next;
            p->next = b;
            a->next = b->next;
            b->next = a;
            p = a;
        }
        
        return dummy->next;	// 第一次循環時p和dummy指向同一個地址
        					// 因此p->next的改變相當於改變dummy->next
        					// 這跟int a = b,a改變後b不變不同
    }
};

206. Reverse Linked List

Reverse a singly linked list.

Example:

Input: 1->2->3->4->5->NULL
Output: 5->4->3->2->1->NULL
Follow up:

A linked list can be reversed either iteratively or recursively. Could you implement both?


思路:類似於以c爲媒介,交互a和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;
        head->next = NULL;
       while(b)	// b爲空時,由於a在b的左側,則此時a爲頭節點
        {
            auto c = b->next;
            b->next = a;
            a = b;
            b = c;
        }

        
        return a;
    }
};

92. Reverse Linked List II

Reverse a linked list from position m to n. Do it in one-pass.

Note: 1 ≤ m ≤ n ≤ length of list.

Example:

Input: 1->2->3->4->5->NULL, m = 2, n = 4
Output: 1->4->3->2->5->NULL


思路:

由於頭節點可能出現變化,使用一個虛擬節點

1 -> 2 -> 3 -> 4 -> 5 -> NULL m = 2 n = 4

a b d c

a節點和c節點分別在要反轉的鏈表兩側,我們先用206. Reverse Linked List的方法把d到d之間的鏈表反轉,接着a節點的next指向d(1->4),b節點的next指向c(2->5)


/**
 * 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;
        
        auto a = dummy, d = dummy;
        for(int i = 0; i < m - 1; i++) a = a->next;
        for(int i = 0; i < n; i++) d = d->next;
        
        auto b = a->next, c = d->next;
        
        for(auto p = b, q = b->next; q != c;)
        {
            auto o = q->next;
            q->next = p;
            p = q;
            q = o;
        }
        
        b->next = c;
        a->next = d;
        
        return dummy->next;
    }
};

160. Intersection of Two Linked Lists

Write a program to find the node at which the intersection of two singly linked lists begins.

For example, the following two linked lists:

image-20200401221644781

begin to intersect at node c1.

image-20200401221719034

image-20200401221739110


思路:設A的頭節點到相交節點的長度爲a(暫且假設相交),B的頭節點到相交節點的長度爲b,共同鏈表的長度爲c

節點p從A頭節點開始出發,走到鏈表尾之後轉到B的頭節點繼續走,走到相交節點,則一共走了a+c+b個節點

節點q從B頭節點開始出發,走到鏈表尾之後轉到A的頭節點繼續走,走到相交節點,則一共走了a+b+c個節點

所以我們發現,只要p、q分別從兩個鏈表頭往下遍歷,每次各自指向下一個節點,並在走到鏈表尾之後轉向另一條鏈表繼續遍歷,如果兩條鏈表有交點,則p、q必定在交點處相遇,即節點p == 節點q,爲同一個節點,因爲此時它們都走了a+b+c個節點,當然如果恰好a==b,則它們在第一次經過相交點時就會相遇

而如果兩條鏈表沒有交點,則在走完a+b個節點後,p和q分別爲NULL,當然此時也滿足p == q,但返回的結果時NULL


/**
 * 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 p = headA, q = headB;
        while(p != q)
        {
            if(p) p = p->next;
            else p = headB;
            if(q) q = q->next;
            else q = headA;
        }
        
        return p;
    }
};

142. Linked List Cycle II

Given a linked list, return the node where the cycle begins. If there is no cycle, return null.

To represent a cycle in the given linked list, we use an integer pos which represents the position (0-indexed) in the linked list where tail connects to. If pos is -1, then there is no cycle in the linked list.

Note: Do not modify the linked list.

image-20200402123328495


思路:快慢指針

假設有環,環的入口爲b點,head(即a點)到b的距離爲x

bj

在head即a點設置快慢指針各一個,快指針一次移動兩個節點,慢指針一次一個,我們假設慢指針移動到b時,快指針移動到c‘處(可能快指針已經繞環很多圈),設此時快指針到b的距離爲y,然後繼續移動,因爲快指針的移動速度是慢指針移動速度的兩倍,慢指針繼續移動y個節點,快指針移動2y個節點,所以它們相遇的點c跟c’是對稱的(都距離b點y個節點)

第一次相遇以後,將慢指針放回到head,即a點,然後兩個指針都以每次一個節點的速度向前移動,它們再次相遇時的點就是環的入口

解釋:因爲慢指針第一次到達b點時,快指針已經移動了2x個節點,即快指針從b點開始移動x個節點以後會停留在c‘處,慢指針第二次到達b點時,由於快指針也是每次一個節點,因此也移動了x個節點,但此時的起點是c,由於c和c’是對稱的,相當於我們把原先的起點b向前移動y個節點到c,那麼原先的終點c’也隨之向前移動y個節點,恰好是環的入口b


/**
 * 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)	// 如果退出循環,說明有節點的next指向NULL,即沒有環
        {
            slow = slow->next;
            fast = fast->next;
            if(fast) fast = fast->next;
            else break;
            
            if(fast == slow)	// 第一次相遇以後
            {
                slow = head;
                while(slow != fast)
                {
                    fast = fast->next;
                    slow = slow->next;
                }
                
                return fast;
            }
        }
        
        return NULL;
    }
};

148. Sort List

Sort a linked list in O(n log n) time using constant space complexity.

Example 1:

Input: 4->2->1->3
Output: 1->2->3->4
Example 2:

Input: -1->5->3->4->0
Output: -1->0->3->4->5


思路:

要求時間複雜度O(n log n),常數空間,如果使用快速排序等,遞歸時用到系統棧,則空間爲O(log n)

使用歸併排序的方法,不用遞歸,循環log n次,每次處理n個數據,所以爲O(n log n)

比如第一次循環,兩個數一組,按升序排號,第二次循環四個數一組,前兩個數和後兩個數都已經是升序,將它們歸併以後,得到的四個數就是升序,再八個數一組……


/**
 * 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) {
        auto dummy = new ListNode(-1);
        dummy->next = head;
        
        int n = 0;
        for(auto p = head; p; p = p->next) n++;
        
        // 歸併排序的思想
        for(int i = 1; i < n; i *= 2)   // 每次對2*i個數進行排序
        {
            auto cur = dummy;
            for(int j = 0; j + i < n; j += i * 2)
            {
                auto left = cur->next, right = cur->next;
                // left和right分別指向一組排好序的數字
                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指向下一組要歸併的數據的前一個節點
                // 這樣後一組2*i個數據排序時left和right都指向這2*i個節點的第一個節點
                cur->next = right;
            }
                
        }
        return dummy->next;
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章