码迷,mamicode.com
首页 > 其他好文 > 详细

【Leetcode解题报告】快慢指针

时间:2016-07-22 08:48:10      阅读:1332      评论:0      收藏:0      [点我收藏+]

标签:

  快慢指针中的快慢指的是移动的步长,即每次向前移动速度的快慢。例如可以让快指针每次沿链表向前移动2,慢指针每次向前移动1次。

Leetcode 141 Linked List Cycle

问题描述

  Given a linked list, determine if it has a cycle in it. 

  Follow up: Can you solve it without using extra space?

分析与解法

  大家可以想一下上体育课长跑的情景。当同学们绕着操场跑步的时候,速度快的同学会遥遥领先,最后甚至会超越其他同学一圏乃至n圈——这是绕圈跑。那么如果不是绕圈跑呢?速度快的同学则会一直领先直到终点,不会再次碰到后面速度较慢的同学。

  这种思想可以用来判断单链表是否有环。如果链表存在环,就好像操场的跑道一样是一个环形一样。此时让快、慢指针都从链表头开始遍历,快指针每次向前移动两个位置,慢指针每次向前移动一个位置;如果快指针到达NULL,说明链表以NULL为结尾,没有环。如果快指针追上慢指针,则表示有环。

  参考代码如下所示:

技术分享
 1 /**
 2  * Definition for singly-linked list.
 3  * struct ListNode 
 4  * {
 5  *     int val;
 6  *     ListNode *next;
 7  *     ListNode(int x) : val(x), next(NULL) {}
 8  * };
 9  */
10 class Solution 
11 {
12 public:
13     bool hasCycle(ListNode *head) 
14     {
15         if (!head || !head->next) return false; 
16         ListNode *fast = head, *slow = head; 
17         while (fast->next && fast->next->next)
18         {
19             fast = fast->next->next; 
20             slow = slow->next; 
21             if (fast == slow) return true; 
22         }
23         return false; 
24     }
25 };
View Code

Leetcode 142 Linked List Cycle II

问题描述

  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. 

  Follow up:

  Can you solve it without using extra space?

分析与解法

  可以用如上方法判断链表是否存在环,如果不存在环,返回null;如果链表存在环,那么怎么寻找环的入口呢?

  假设链表的长为L,起始点到环入口长度为a,环长度为r,则 L = a + r。

  在快指针进入环到慢指针进入环前的这段时间,若环的长度较短,也许快指针已经走了好几圈了。然后慢指针进入环,设慢指针和快指针在环内相遇时,慢指针在环内走了X步,走的总步数为(包括环内与环外)为S步,显然 S = X + a,那么快指针走了多少步呢?

  快指针在环内已经走了n圈加X步,即 nr + X 步,其中n最少为1,而走的总步数为 nr + X + a 步。

  由于快指针走的总步数是慢指针的2倍,故 nr + X + a = (X + a) * 2。

  由上式得 a + X = nr,即 a = nr - X = (n - 1)r + r - X。

  上式的含义为环入口距离起点的距离(等于a)和相遇点距离环入口的距离(等于r - X)相差整数倍的r。

  故让慢指针回到起点,快指针从相遇点开始继续走,步长都为1,则当相遇时,即为环入口。此时慢指针走了a步,而快指针也走了a步(a = (n - 1)r + r - X)。

  参考代码如下所示:

技术分享
 1 class Solution 
 2 {
 3 public:
 4     ListNode *detectCycle(ListNode *head) 
 5     {
 6         if (!head || !head->next) return NULL; 
 7         ListNode *fast = head, *slow = head, *entry = head; 
 8         while (fast->next && fast->next->next)
 9         {
10             fast = fast->next->next; 
11             slow = slow->next; 
12             if (fast == slow)
13             {
14                 while (slow != entry)
15                 {
16                     slow = slow->next; 
17                     entry = entry->next; 
18                 }
19                 return entry; 
20             }
21         }
22         return NULL; 
23     }
24 };
View Code

Leetcode 19 Remove Nth Node from End of List

问题描述

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

  For 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. 

  Note: given n will always be valid. Try to do this in one pass. 

分析与解法

  经典的快慢指针问题。我们可以定义两个指针,第一个指针从链表的头指针开始遍历向前走n步,第二个指针保持不动;从第n+1步开始,第二个指针也开始从链表的头指针开始遍历。由于两个指针的距离保持在n,当第一个指针到达链表的结尾点时,第二个指针正好是倒数第n个结点的前驱结点。然后可以方便地删除倒数第k个结点,参考代码如下所示:

技术分享
 1 class Solution {
 2 public:
 3     ListNode* removeNthFromEnd(ListNode* head, int n) 
 4     {
 5         ListNode new_head_node(0); new_head_node.next = head; 
 6         ListNode *p = head, *q = &new_head_node, *s = NULL; 
 7         for (int i = 0; i < n; i++) p = p->next; 
 8         while (p)
 9         {
10             p = p->next; 
11             q = q->next; 
12         }
13         s = q->next; 
14         q->next = s->next; 
15         delete s; 
16         return new_head_node.next; 
17     }
18 };
View Code

Leetcode 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 begin to intersect at node c1. 

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

  Notes:

  If the two linked lists have no intersection at all, return null; 

  The linked lists must retain their original structure after the function returns; 

  You may assume there are no cycles anywhere in the entire linked structure; 

  You code should preferably run in O(n) time and use only O(1) memory. 

分析与解法

(1) 直观的想法

  判断第一个链表的每个节点是否在第二个链表中。这种方法的时间复杂度为O(Length(L1) * Length(L2))。

(2) 利用计数的方法

  很容易想到,如果两链表相交,那么这两个链表就会有共同的节点。而节点地址又是节点的唯一标识。所以,如果我们能够判断两个链表中是否存在地址一致的节点,就可以知道两个链表是否相交。一个简单的做法就是对第一个链表节点地址进行hash排序,建立hash表,然后针对第二个链表的每个结点的地址查询hash表,如果它在hash表中出现,那么说明第二个链表和第一个链表有共同的节点。这个方法的时间复杂度为O(Length(L1) + Length(L2))。但是它同时需要附加O(Length(L1))的存储空间,以存储哈希表。

(3) 转化为环问题

  由于两个链表都没有环,我们可以把第二个链表接在第一个链表后面,如果得到的链表有环,则说明两个链表相交。否则,不相交。这样我们就把问题转化为判断一个链表是否有环。但这种方法不容易求出相交节点的位置。

(4) 快慢指针

  如果两个没有环的链表相交于某一节点的话,那么在这个节点之后的所有节点都是两个链表所共有的。那么我们能否利用这个特点简化我们的解法呢?

  我们知道,如果它们相交,则最后一个结点一定是共有的。而我们容易得到链表的最后一个结点,所以这成了我们简化解法的一个主要突破口。

  首先两个链表各遍历一次,求出两个链表的长度,然后可以得出两个链表的长度差L。然后先在长链表上遍历L个结点,之后再同步遍历,于是在遍历中,第一个相同的结点就是第一个公共的结点。时间复杂度为O(Length(L1) + Length(L2)),空间复杂度为O(1)。

  参考代码如下所示:

技术分享
 1 class Solution 
 2 {
 3     ListNode *getIntersectionNode(ListNode *headA, ListNode *headB, int lenA, int lenB)
 4     {
 5         if (lenA < lenB) return getIntersectionNode(headB, headA, lenB, lenA); 
 6         int diff = lenA - lenB; 
 7         ListNode *a_cur = headA, *b_cur = headB; 
 8         for (int i = 0; i < diff; i++) a_cur = a_cur->next; 
 9         while (a_cur && b_cur)
10         {
11             if (a_cur == b_cur) return a_cur; 
12             a_cur = a_cur->next; 
13             b_cur = b_cur->next; 
14         }
15         return NULL; 
16     }
17 public:
18     ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) 
19     {
20         ListNode *a_cur = headA, *b_cur = headB; 
21         int a_len = 0, b_len = 0; 
22         while (a_cur)
23         {
24             ++a_len; 
25             a_cur = a_cur->next; 
26         }
27         while (b_cur)
28         {
29             ++b_len; 
30             b_cur = b_cur->next; 
31         }
32         return getIntersectionNode(headA, headB, a_len, b_len); 
33     }
34 };
View Code

思考:如果链表可能有环呢?如何求出第一个相交的结点?

  如果都没有环,则和上述方法相同;

  如果一个链表有环,另一个链表无环,则不相交;

  如果两个链表都有环,则判断任意一个链表上快慢指针相遇的那个结点,在不在另一条链表上。如果在,则相交,如果不在,则不相交。若相交,两个链表的入口结点可能并不是环上的同一个结点。如果是同一个结点,则交点在入环之前,找相交的第一个结点又转换成了无环的情况;如果不是同一个结点,再找出两个链表环的入口点,可以定义任一一个入口点即为相交的第一个结点。

 

【Leetcode解题报告】快慢指针

标签:

原文地址:http://www.cnblogs.com/xiaoxxmu/p/5690144.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!