快慢指针类型(Fast & Slow pointers)

Pattern: Fast & Slow pointers, 快慢指针类型

介绍部分来自:链接:https://www.zhihu.com/question/36738189/answer/908664455 作者:穷码农

这种模式,有一个非常出门的名字,叫龟兔赛跑。 咱们肯定都知道龟兔赛跑啦。但还是再解释一下快慢指针:这种算法的两个指针的在数组上(或是链表上,序列上)的移动速度不一样。还别说,这种方法在解决有环的链表和数组时特别有用

通过控制指针不同的移动速度(比如在环形链表上),这种算法证明了他们肯定会相遇的。快的一个指针肯定会追上慢的一个(可以想象成跑道上面跑得快的人套圈跑得慢的人)。

img

​ 上面这个图演示了快慢两个指针最终在5相遇了

咋知道需要用快慢指针模式勒?

  • 问题需要处理环上的问题,比如环形链表和环形数组
  • 当你需要知道链表的长度或是某个特别位置的信息的时候

那啥时候用快慢指针而不是上面的双指针呢?

  • 有些情形下,咱们不应该用双指针,比如我们在单链表上不能往回移动的时候。一个典型的需要用到快慢指针的模式的是当你需要去判断一个链表是否是回文的时候。

总结: 慢指针执行一个步骤,快指针执行另两个步骤。快慢指针一般用于两者最终可以相遇(相等)的情况下使用。

经典题目:

链表的数据结构:

// Definition for singly-linked list.
class ListNode {
    int val;
    ListNode next;
    ListNode(int x) {
        val = x;
        next = null;
    }
}

1、LinkedList Cycle (easy)

141. 环形链表

102. 带环链表

描述:

​ 给定一个链表,判断链表中是否有环。

​ 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

​ 如果链表中存在环,则返回 true 。 否则,返回 false 。

​ 进阶:

​ 你能用 O(1)(即,常量)内存解决此问题吗?

示例:

示例 1:

img

​	输入:head = [3,2,0,-4], pos = 1
​	输出:true
​	解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:

img

​	输入:head = [1,2], pos = 0
​	输出:true
​	解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:

img

​	输入:head = [1], pos = -1
​	输出:false
​	解释:链表中没有环。

使用快慢指针:

public boolean hasCycle(ListNode head) {
    if (head == null)
        return false;

    ListNode slow = head;        // 慢指针
    ListNode fast = head;        // 快指针

    while (fast != null && fast.next != null){
        fast = fast.next.next;   // 快指针走两步
        slow = slow.next;        // 慢指针走一步
        if (fast == slow)        // 最终相遇则有环
            return true;
    }
    return false;
}

使用破坏链表法:

public boolean hasCycle(ListNode head) {
    if (head == null)
        return false;

    ListNode p = head;
    while (p != null){
        ListNode next = p.next;             // 下一个节点
        if (next == head) return true;      // 等于头节点则返回
        p.next = head;                      // 破坏链表
        p = next;                           // 下一个节点继续遍历
    }
    return false;
}

2、Start of LinkedList Cycle (medium)

在这里插入图片描述

注意: 慢指针一定是走不到一圈就相遇了,因为如果在环的入口点没有相遇的话,快指针的速度是慢指针的两倍,慢指针在入口点时快指针已经进入环内,在慢指针走完一圈之前,快指针一定会追上它。最差的情况就是在入口点相遇,这是快指针走了两圈,慢指针刚好走了一圈。

​ 快指针每次走两步:a + nc + b

​ 慢指针每次走一步:a + b

​ 距离关系有 a + nc + b = 2(a + b) 得:a = b - nc

​ 当 n = 1a = c - b 。也就是 相遇点到环起点(顺时针) = 起始点到环起点故使用一个指针从头结点开始遍历,慢指针从相遇点开始遍历,两者就会在环起点相遇。

​ 当 n = 0时,a = b也就是表头就是环入口点。

public class Solution {
    public ListNode detectCycle(ListNode head) {
        if (head == null)
            return null;

        // 找出相遇点
        ListNode slow = head;       // 慢指针,每次走一步
        ListNode fast = head;       // 快指针,每次走两步
        boolean isCycle = false;    // 是否有环标志

        while (fast != null && fast.next != null){
            fast = fast.next.next;   // 走两步
            slow = slow.next;        // 走一步
            if (fast == slow){
                isCycle = true;     // 有环标志
                break;              // 退出循环
            }
        }

        // 有环则找出起始点
        if (isCycle){
            ListNode first = head;      // 从头结点开始遍历,最终和慢指针在环入口点相遇
            while (first != slow){
                first = first.next;
                slow = slow.next;
            }
            return slow;
        }else {
            return null;
        }
    }
}

3、Happy Number (medium)

202. 快乐数

488. 快乐数关注问题

描述

​ 写一个算法来判断一个数是不是"快乐数"。

​ 一个数是不是快乐是这么定义的:对于一个正整数,每一次将该数替换为他每个位置上的数字的平方和,然后重复这个过程直到这个数变为1,或是无限循环但始 终变不到1。如果可以变为1,那么这个数就是快乐数。

样例

例1:

输入:19
输出:true
说明:
19是一个快乐的数字
     1 ^ 2 + 9 ^ 2 = 82
     8 ^ 2 + 2 ^ 2 = 68
     6 ^ 2 + 8 ^ 2 = 100
     1 ^ 2 + 0 ^ 2 + 0 ^ 2 = 1

例2:

输入:5
输出:false
说明:
5不是一个快乐的数字
25->29->85->89->145->42->20->4->16->37->58->89
再次出现89。

如果给定的数字会一直循环,则可以使用快慢指针判断是否重复。

slow 指针进行一次替换操作

fast 指针进行两次替换操作

他们最终会在重复的数字相遇,即slow==fast

如果他们最终都为 slow == fast == 1,即快乐数。否则,为不快乐数。

class Solution {
    public boolean isHappy(int n) {
        if (n == 0)
            return false;
        if (n == 1)
            return true;

        int slow = n;       // 慢指针,进行一次替换操作
        int fast = n;       // 快指针,进行两次替换操作

        // 当 slow != fast 时一直循环
        do {
            slow = squareSum(slow);
            fast = squareSum(fast);
            fast = squareSum(fast);
        }while (slow != fast);

        // 在 1 相遇 即 slow == fast == 1 即快乐数
        if (fast == 1){
            return true;
        }
        return false;
    }

    public int squareSum(int n){
        int squareSum = 0;
        while (n != 0){
            squareSum += (n % 10) * (n % 10);       // 取个位数
            n /= 10;                                // 缩小十位
        }
        return squareSum;
    }
}

4、Middle of the LinkedList (easy)

876. 链表的中间结点

1609. 链表的中间结点

描述

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

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

​ The number of nodes in the given list will be between 1 and 100.

样例

样例 1:

输入:1->2->3->4->5->null
输出:3->4->5->null

样例 2:

输入:1->2->3->4->5->6->null
输出:4->5->6->null

解题思路:

使用快慢指针,快指针每次走两步,慢指针每次走一步。当快指针到达链表尾部的时候,慢指针就到达链表的中部。

public class Solution {
    /**
     * @param head: the head node
     * @return: the middle node
     */
    public ListNode middleNode(ListNode head) {
        // write your code here.
        if (head == null)
            return null;

        ListNode slow = head;       // 慢指针,每次走一步
        ListNode fast = head;       // 快指针,每次走两步

        while (fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值