链表快慢指针--找出重复的数字

简介

简单来说,就是在链表里,慢指针一次走一步,快指针一次两步。

题目

在一个长为n+1的数组a中,每个数组元素ai:1 <= ai <=n.
并且,只有一个重复出现的数字,找出这个数字。
原数组不可动。

其实挺简单的,空间复杂度O(n)时,时间复杂度O(n)的方法是比较容易想得到的。
但是今天我学到一种可以时间复杂度O(n),但是空间复杂度O(1)的–借助快慢指针。

分析

客观条件:数组值有重复,下标肯定不重复
所以,换一下遍历的规则:

1.令p1=0:p1=a[p1]

2.令p2=0:p2=a[a[p2]]

其中p1是慢指针,p2是快指针。(p2走了一圈,p1还在中点的那种快和慢)

将数组值考虑为链表中结点编号,遍历的顺序描述为结点间关系(谁能到达谁),
假设现有列表[2,1,5,4,6,6,3],那么由上述可得链表:
在这里插入图片描述
p1,p2都会在这个有环链表中一直走下去,区别是一个一次1步,一个一次2步。
那么此题目中重复的数字会是“割点”(割点是图论中的概念,这里暂且一用)的编号(图中的6),那么如何找到这个割点呢?

接下来就是数学证明啦!
p1、p2在一个有环链表中这样走的话,肯定最终相遇在环上的某点。

为了证明,先抛开上述条件,假设在这样一个链表中:
在这里插入图片描述
其中,0为公共起点,第x点为“割点”,第x+t点为p1、p2最终相遇点。假设环长为y(环上有y个结点)
那么p1在相遇时走了x+t+ky步,p2走了x+t+k’y步,其中k>=0,k’>=1,且k<k’.
由于p1、p2脚程是两倍关系:(x+t+ky)*2=x+t+k’y
得出一个小结论,记作Q:x+t=ky (将上面的2k-k’记作k,取正整数),即x+t会是y的正整数倍。

我们的目的是找出x:

当p1、p2相遇时,p1步数是x+t+ky,p1只要再走y-t+hy步(h为整数),便是x点;
假设p3在p1、p2相遇时,就在0点了,此后p1每走一步,p3也走一步,那么p3走x+h’y(h’为整数)步后,也可以到达x点;
并且此时 h>=h’ .

假设二者能够相遇的话,那么 y-t+hy = x+h’y 得出:
x+t=ky (k为正整数),即是上述的结论Q,也就证明了二者真的能够相遇

所以在p1、p2相遇时,只要p3位于0点,接着p1走一步,p3也走一步,那么二者相遇处的结点编号即为重复出现的数值。

大概代码:

var a=[2,1,5,4,6,6,3];
let p1=0,p2=0,p3=0;
do{
    p1=a[p1];
    p2=a[a[p2]];
}while(p1!=p2);
do{
    p1=a[p1];
    p3=a[p3];
}while(p1!=p3);
console.log(p3);//6

可见时间复杂度O(n),而空间复杂度只是O(1)。

题外话
不过这道题如果数组元素大小没有限制,然后也是只有一个重复的话,这种解法就用不了了。
如果没有“原数组不可动”这一限制的话,可以排序后遍历,是O(nlogn)的复杂度。
但是也是限制了,那么便可以二分枚举数值了。
大概这样:
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值