快慢指针---不就是快指针走两步慢指针走一步嘛?

Posted 程序员小熊

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了快慢指针---不就是快指针走两步慢指针走一步嘛?相关的知识,希望对你有一定的参考价值。

前言

大家好,我是程序员小熊,来自大厂的程序猿。快慢指针是面试中常考的知识点,尤其是涉及到链表相关问题,比如判断单链表是否有环-141. 环形链表和求环形单链表入环的第一个节点-142. 环形链表 II等等。

快慢指针有所了解的童鞋都知道,其解题的套路一般是快指针每次走两步,慢指针每次走一步,然后再根据一些判断条件去做相应的操作。可快慢指针真的只是快指针走两步慢指针走一步嘛?本文以求单链表的上中间节点和下中点为例子,深入剖析,供大家参考,希望对大家有所帮助。

题目一

给定一个头结点为 head 的非空单链表,返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。 

 

 

解题思路

本题是 leetcode 的原题 876. 链表的中间结点,从示例中可以看出,如果链表长度为奇数,要求的是中间节点;否则,要求的是下中点。也就是说如果链表长度是偶数(有两个中间节点),求第二个中间节点。

举例

以链表 1->2->3->4->null 为例子,采用快慢指针解法的动图,如下图示。

动图是采用两根(快慢)指针,fast/slow 刚开始均指向链表头节点,然后每次快节点走两步,慢指针走一步,直至快指针指向 null,此时慢节点刚好来到链表的下中节点。

Show me the Code

C++

 1 ListNode* middleNode(ListNode* head) {
 2     /* 链表只有头节点,直接返回 */
 3     if (head->next == nullptr) {
 4         return head;
 5     }
 6 
 7     /* 定义均指向链表头节点的快慢指针 */
 8     ListNode *slow = head, *fast = head;
 9     /* 每次快指针走两步,慢指针走一步, 直至快指针或快指针的下一节点为 null */
10     while ((fast != nullptr) && (fast->next != nullptr)) {
11         fast = fast->next->next;
12         slow = slow->next;
13     }
14     
15     return slow;
16 }
View Code
 

Python3

1 def middleNode(self, head: ListNode) -> ListNode:
2     slow, fast = head, head
3     while fast and fast.next:
4         fast, slow = fast.next.next, slow.next
5         
6     return slow
View Code

 

当然,也可以让快慢指针刚开始均指向链表的头节点的下一节点,然后每次快节点走两步,慢指针走一步,直至快指针指向节点的下一节点为 null,此时慢节点刚好来到链表的下中节点。

Show me the Code

Java

 1 ListNode middleNode(ListNode head) {
 2     /* 链表只有头节点,直接返回头节点 */
 3     if (head.next == null) {
 4         return head;
 5     }
 6 
 7     /* 定义快慢指针均指向链表头节点的下一节点 */
 8     ListNode fast = head.next, slow = head.next;
 9     /* 快指针指向的节点的下一节点为空或下下一节点为空,快慢指针停止移动 */
10     while (fast.next != null && fast.next.next != null) {
11         fast = fast.next.next;
12         slow = slow.next;
13     }
14 
15     return slow;
16 }
View Code

Golang

 1 func middleNode(head *ListNode) *ListNode {
 2     if head.Next == nil {
 3         return head
 4     }
 5 
 6     slow, fast := head.Next, head.Next
 7     for fast.Next != nil && fast.Next.Next != nil {
 8         fast, slow = fast.Next.Next, slow.Next
 9     }
10 
11     return slow    
12 }
View Code

复杂度分析

空间复杂度O(1),未开辟额外存储空间。

时间复杂度O(n),n 给定链表的长度(结点数目)。

题目二

给定一个头结点为 head 的非空单链表,返回链表的中间结点。

如果有两个中间结点,则返回第一个中间结点。

示例

输入:[1,2,3,4,5,6]

输出:此列表中的结点 3 (序列化形式:[3,4,5,6]) 由于该列表有两个中间结点,值分别为 3 和 4,我们返回第一个结点

解题思路

本题与上题唯一的不同在于:如果链表长度为偶数,则要求链表的第一个中点,也就是下中点。因此仍然可以采用上一题的快慢指针解法,只不过在快慢指针的初始化以及循环遍历的条件需要修改。

举例

以链表 1->2->3->4->null 为例子,同样采用快慢指针解法的动图,如下图示。

Show me the Code

java

 1 ListNode midOrUpMidNode(ListNode head) {
 2     /* 链表只有头节点,直接返回头节点 */
 3     if (head.next == null || head.next.next == null) {
 4         return head;
 5     }
 6 
 7     /* 定义快慢指针分别指向链表头节点的下下个节点和头节点的下一节点 */
 8     ListNode fast = head.next.next, slow = head.next;
 9     /* 快指针指向的节点的下一节点为空或下下一节点为空,快慢指针停止移动 */
10     while (fast.next != null && fast.next.next != null) {
11         fast = fast.next.next;
12         slow = slow.next;
13     }
14 
15     return slow;
16 }
View Code

Golang

 1 func midOrUpMidNode(head *ListNode) *ListNode {
 2     if head.Next == nil || head.Next.Next == nil {
 3         return head
 4     }
 5 
 6     slow, fast := head.Next, head.Next.Next
 7     for fast.Next != nil && fast.Next.Next != nil {
 8         fast, slow = fast.Next.Next, slow.Next
 9     }
10 
11     return slow    
12 }
View Code

复杂度分析

时间复杂度O(n)

空间复杂度O(1)

总结

从以上两个题可以看出,虽然快慢指针确实是快指针每次走两步,慢指针每次走一步,但是针对不同的题目,其初始化以及遍历终止条件也不相同,所以在刷题及其面试过程中,需要考虑当慢指针指向要求的节点时,快指针此刻应该在什么位置,只有理清了思路,才更容易写出 bug-free 的代码。

往期快慢指针相关精彩文章

单链表之环形链表

以上是关于快慢指针---不就是快指针走两步慢指针走一步嘛?的主要内容,如果未能解决你的问题,请参考以下文章

算法复习:双指针(对撞指针快慢指针)

判断单向链表是否有环,以及环入口与链表头节点的距离

[LeetCode] Linked List Cycle

leetcode 141.环形链表

判断链表是否有环

[LeetCode] 141. Linked List Cycle