快慢指针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)

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