快慢指針TwoPointers

概念

TwoPointers 指的是一快一慢的兩個指針去推進一個鏈表,我把它分爲兩類。

  • 第一類:起點不一樣,fast比slow先走n步
  • 第二類:步長不一樣,fast比slow推進的快,例如fast = fast->next->next; slow = slow->next,fast的步長爲2,而slow的步長爲1。

TwoPointers可以解決哪些問題?

  1. 找到鏈表的中點
  2. 找到鏈表倒數第n個節點
  3. 判斷和檢測鏈表(Cycle)的問題
  4. 找到兩個鏈表的交點

例題

  • 找到鏈表的中點

Given a non-empty, singly linked list with head node head, return a middle node of linked list.
If there are two middle nodes, return the second middle node.

Example 1:

Input: [1,2,3,4,5]
Output: Node 3 from this list (Serialization: [3,4,5])
The returned node has value 3. (The judge’s serialization of this node is [3,4,5]).
Note that we returned a ListNode object ans, such that:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, and ans.next.next.next = NULL.

Example 2:

Input: [1,2,3,4,5,6]
Output: Node 4 from this list (Serialization: [4,5,6])
Since the list has two middle nodes with values 3 and 4, we return the second one.

思路:
要找到中點,我們首先想到如果知道鏈表的長度,那麼問題將會很簡單,那我們需要先來一趟循環把長度求出來嗎?TwoPointers告訴你,不需要,一趟循環解決問題。

void middleList(struct ListNode* head, struct ListNode** mid){
    struct ListNode *fast = head, *slow = head;
    while(fast && fast->next){
        fast = fast->next->next;
        slow = slow->next;
    }
    *mid = slow;
}

  • 找到鏈表倒數第n個節點

Given a linked list, remove the n-th node from the end of list and return its head.

Example:

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.

思路:同樣的我們不需要事先知道鏈表的長度,我們讓快指針先走n步,再讓fast and slow並駕齊驅,那麼slow和fast的距離始終是n, 當fast走到尾節點的時候,slow和尾節點的距離是n,那麼就是它了。

struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
    if(!head)
        return NULL;
    struct ListNode* fast = head;
    struct ListNode* slow = head;
    struct ListNode* tmp = NULL;
    while(n--)
        fast = fast->next;
    if(!fast){
        tmp = head;
        head = head->next;
        free(tmp);
        return head;
    }
    while(fast->next){
        fast = fast->next;
        slow = slow->next;
    }
    tmp = slow->next;
    slow->next = slow->next->next;
    free(tmp);
    return head;
}
同樣,它對刪除頭節點的情況作了判斷。
  • 判斷和檢測鏈表(Cycle)的問題

Given a linked list, return the node where the cycle begins. If there is no cycle, return null.
Note: Do not modify the linked list.

思路:設置快慢指針,起點都爲head,fast比slow步長多1,那麼如果有環存在的話,當slow走到入環位置時,fast已經在環中了,當他們第一次相遇時,fast已經比slow多走了n圈。此時讓slow再指向head,和fast以一樣的步長來走,當它們再次相遇的位置即爲入環位置。
入環位置與head的距離:a
環的長度:r
鏈表的長度 :L
第一次相遇和head的距離: s
第一次相遇和入環位置的距離:x
存在以下關係:
s = 2s + nr
r = L - a
a + x = s = nr = (n-1)r + r = (n-1)r + L - a
a = (n-1)r + (L - a - x)
最後一式說明讓slow從頭走,fast從當前位置走,步長爲1,同時出發,它們一定會在a位置相遇,也就是入環位置,算法的正確性得以驗證。

struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode* fast = head;
    struct ListNode* slow = head;
    while(fast != NULL && fast->next != NULL){
        fast = fast->next->next;
        slow = slow->next;
        if(fast == slow){
            slow = head;
            while(fast && slow != fast){
                fast = fast->next;
                slow = slow->next;
            }
            return slow;   
        }
    }
    return NULL;
}

  • 找到兩個鏈表的交點

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

For example, the following two linked lists:

A: a1 → a2→ c1 → c2 → c3
B: b1 → b2 → b3→ c1 → c2 → c3

begin to intersect at node c1.

這就屬於另類快慢指針的運用了,你應該弄清楚它們的指向是如何改變的。經驗證,該算法的時間複雜度爲O(m+n)。

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    if(headA == NULL || headB == NULL)
        return NULL;
   struct ListNode *pa = headA, *pb = headB;
    while(true){
        if(pa == pb)
            return pa;
        if(pa->next == NULL && pb->next != NULL){
            pa = headB;
            pb = pb->next;
            continue;
        }
        if(pa->next != NULL && pb->next == NULL){
            pb = headA;
            pa = pa->next;
            continue;
        }
        if(pa->next == NULL && pb->next == NULL)
            return NULL;
        pa = pa->next;
        pb = pb->next;      
    }
    return NULL;     
}

實際上,pa,pb的存在彷彿將兩個鏈表連接到了一起,假若有交點,它們必定會相遇,因爲實際上它們推進的根本就是同一條鏈表,就是那個虛擬鏈接在一起,長爲m + n的鏈表。只是快慢交錯 ,最壞情況下 ,它們在m+n個推進後相遇。

轉載自:(https://www.jianshu.com/p/f26278f427bf)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章