“快慢指针”思想在物理或者逻辑循环中的应用

1 基础概念

1.1 什么是物理循环和逻辑循环?

物理循环是指物理索引访问顺序上相邻,逻辑上也相邻,比如循环链表,逻辑循环则指物理的索引上不一定相邻

1.2 快慢指针本质上可以解决逻辑循环问题,而物理循环也属于逻辑循环问题。

2 用快慢指针找出物理循环

2.1 LC141. 环形链表

public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head==null||head.next==null)return false;
        ListNode f=head;
        ListNode s=head;

        while(f!=null&&f.next!=null){
            f=f.next.next;
            s=s.next;
            if(f==s){
                return true;
            }
        }
        return false;
    }
}

3 用快慢指针找出逻辑循环

3.1 LC457. 环形数组是否存在循环

“快慢指针”思想在物理或者逻辑循环中的应用_第1张图片

3.1.1 解析

务必参考题解:快慢指针解法

class Solution {
    public boolean circularArrayLoop(int[] nums) {
        int n=nums.length;
        for(int i=0;i<n;i++){
            if(nums[i]==0)continue;
            int s=i;
            int f=next(nums,i);
            while(nums[s]*nums[f]>0&&nums[s]*nums[next(nums,f)]>0){
                if(s==f){
                    if(s!=next(nums,s)){
                        return true;
                    }else{
                        break;
                    }
                }
                s=next(nums,s);
                f=next(nums,next(nums,f));
            }

            int add=i;

            while(nums[add]*nums[next(nums,add)]>0){
                nums[add]=0;
                add=next(nums,add);
            }
        }
        return false;
    }
    int next(int nums[],int i){
        int n=nums.length;
        return ((i+nums[i])%n+n)%n;
    }
}

3.1.2 Q1:针对这道题,为什么可以用快慢指针呢?快慢指针本身不是针对链表嘛?而且每次只能走一步啊

您的疑问是非常合理的。快慢指针方法通常用于链表环路检测,但其实它也可以适用于这种环形数组问题。这里的关键在于理解快慢指针方法的本质:它是一种用于检测循环或周期性的算法。

在环形数组问题中,即使每次移动的步数不是一步,我们仍然可以使用快慢指针。在这个问题中,"快指针"每次移动两个nums[i]的距离,而"慢指针"每次移动一个nums[i]的距离。这种设置保证了如果存在一个循环,快指针最终会追上慢指针。这就是为什么可以使用快慢指针方法来解决这个问题。

具体到这个问题:

  • 每个元素nums[i]指示从当前位置应该移动的步数,正数表示向前移动,负数表示向后移动。
  • 环形数组意味着数组是循环的,即从数组末尾的下一个元素是数组开头的元素。
  • 快慢指针在这里是用来检测循环的存在。如果存在一个循环,那么快指针最终会在某个点上追上慢指针。
  • 在追踪快慢指针的过程中,还需要考虑移动的方向。如果在移动过程中方向改变(即从正转负或从负转正),则当前路径不构成有效循环。

因此,尽管这个方法最初是为链表设计的,但其检测循环的基本原理在环形数组问题中同样适用。

3.1.3 Q2:这里的步数是指逻辑上位置的步数对吧?

是的,您理解得正确。在这个环形数组问题中,"步数"指的是逻辑上位置的步数。具体来说:

  • 数组中的每个元素 nums[i] 表示从位置 i 出发应该移动的步数。
  • 如果 nums[i] 是正数,则表示向前(即下标递增的方向)移动 |nums[i]| 步。
  • 如果 nums[i] 是负数,则表示向后(即下标递减的方向)移动 |nums[i]| 步。
  • 由于数组是环形的,所以到达数组的末端后会再次从头开始,反之亦然。

在这个问题的上下文中,步数并不是指物理意义上的步行距离,而是指在数组索引上的移动距离。例如,如果 nums[i] = 3,则意味着从索引 i 向前移动3个索引位置。由于是环形数组,当超过数组末端时,计数会从数组开头继续。

3.1.4 Q3:对于快慢指针而言,只要存在环,无论快和慢指针的的初始位置怎么变,最终快慢可以相遇对吗(解释代码中slow和fast初始位置为什么设置的不同)

是的,对于快慢指针法来说,只要存在环,快指针最终都会追上慢指针,从而使两者相遇。这个结论是基于以下几个关键点的:

  1. 不同速度:快指针的移动速度是慢指针的两倍。这意味着如果存在一个环,快指针每轮会逐渐接近慢指针。

  2. 循环性:由于环的存在,快慢指针都会不断地绕环运动。这确保了,只要存在环,快慢指针永远不会“走出”环并停止。

  3. 相遇条件:快慢指针在环中的相对位置每次循环都会变化。由于快指针每次循环比慢指针多走一圈,它们最终必然会在某个点相遇。

这些原则不仅适用于链表中的环检测,也同样适用于类似的环形数组问题。关键是环的存在以及快慢指针的相对速度差。在实际应用中,初始位置一般会从一个固定点开始,例如数组的起始位置,以确保能够覆盖所有可能的环。

3.4.5 (1) 在环中,如果让快指针走三步,慢指针走1步,也最终能相遇吗?(2) 在环中,如果让快指针走三步,慢指针走2步,也最终能相遇吗

在环形结构中,快慢指针是否能相遇取决于它们的相对速度差,而不仅仅是它们各自的速度。我们来分析一下您提出的两种情况:

  1. 快指针每次走三步,慢指针每次走一步:在这种情况下,快指针和慢指针之间的相对速度差是两步。这意味着如果存在一个环,快指针最终会追上慢指针。这是因为快指针每次循环都会逐渐接近慢指针,并最终在某个点与慢指针相遇。

  2. 快指针每次走三步,慢指针每次走两步:在这种情况下,快指针和慢指针之间的相对速度差是一步。这同样意味着如果存在一个环,快指针仍然会最终追上慢指针。虽然这种情况下快慢指针相遇的时间可能会比速度差更大的情况更长,但只要环存在,它们最终还是会相遇。

总结来说,只要快慢指针之间存在速度差,它们就会在环中最终相遇。速度差越大,相遇的可能性就越快出现。在实际应用中,通常选择让快指针的速度是慢指针的两倍,这样可以在保证检测效率的同时简化问题的复杂度。

你可能感兴趣的:(数据结构,数据结构,快慢指针)