【Leetcode解题报告】快慢指针

  快慢指针中的快慢指的是移动的步长,即每次向前移动速度的快慢。例如可以让快指针每次沿链表向前移动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 };

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

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

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

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

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

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

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

时间: 2024-10-07 04:51:51

【Leetcode解题报告】快慢指针的相关文章

LeetCode解题报告:Reorder List

Reorder List Given a singly linked list L: L0→L1→…→Ln-1→Ln,reorder it to: L0→Ln→L1→Ln-1→L2→Ln-2→… You must do this in-place without altering the nodes' values. For example,Given {1,2,3,4}, reorder it to {1,4,2,3}. 思路: 1.利用快慢两个指针将链表一分为二: 2.针对第二个子链表求倒序

leetCode解题报告5道题(九)

题目一:Combinations Given two integers n and k, return all possible combinations of k numbers out of 1 ... n. For example,If n = 4 and k = 2, a solution is: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ] 分析: 题意给我们一个数字n, 和一个数字k,让我们求出从 1~~n中取出k个数所能得到的组合数 所

leetCode解题报告5道题(六)

题目一: Longest Substring Without Repeating Characters Given a string, find the length of the longest substring without repeating characters. For example, the longest substring without repeating letters for "abcabcbb" is "abc", which the

LeetCode解题报告:Linked List Cycle &amp;&amp; Linked List Cycle II

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? Linked List Cycle II Given a linked list, return the node where the cycle begins. If there is no cycle, return null. Follo

leetCode解题报告5道题(十)

Disk Schedule Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 2368    Accepted Submission(s): 333 Problem Description 有很多从磁盘读取数据的需求,包括顺序读取.随机读取.为了提高效率,需要人为安排磁盘读取.然而,在现实中,这种做法很复杂.我们考虑一个相对简单的场景.磁

leetCode解题报告5道题(十一)

题目一:Subsets Given a set of distinct integers, S, return all possible subsets. Note: Elements in a subset must be in non-descending order. The solution set must not contain duplicate subsets. For example, If S = [1,2,3], a solution is: [ [3], [1], [2]

LeetCode解题报告:LRU Cache

LRU Cache Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and set. get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise retu

leetCode解题报告5道题(八)

题目一: Populating Next Right Pointers in Each Node Given a binary tree struct TreeLinkNode { TreeLinkNode *left; TreeLinkNode *right; TreeLinkNode *next; } Populate each next pointer to point to its next right node. If there is no next right node, the

leetCode解题报告5道题(七)

先送上亚马逊传送门:<黑客与画家>:硅谷创业之父 Paul Graham 文集 再送上一个思维导图: 最好的办法就是自己创业或者加入创业公司 一个命题 命题 创业是一个压缩的过程,所有工作压缩成短短几年. 你不再是低强度的工作四十年,而是以极限强度工作四年 举例解释 一个优秀的黑客去除各种障碍,工作效率可以是在公司时的36倍. 假设他年薪8万美元,那么一个勤奋工作,摆脱杂事干扰的聪明黑客, 他的工作相当于年薪200万美元的价值 这里说的是极限情况,休闲时间为0,工作强度足以危害到健康. 守恒定

LeetCode解题报告:Binary Tree Postorder Traversal

Given a binary tree, return the postorder traversal of its nodes' values. For example:Given binary tree {1,#,2,3}, 1 2 / 3 return [3,2,1]. Note: Recursive solution is trivial, could you do it iteratively? 注意:下面是迭代的解法.理解有点困难,和大家讨论一下. 1 import java.uti