不修改数组找出重复的数字算法精解
Contents
第一次写算法题解,算是新的尝试,前几个月的题解都在力扣里,好处是有很多测试用例,坏处是你写题解不太方便,我的很多解法和注释都在代码里面里。 正好在别的地方看到里这个题目但是在力扣里没找到,所以不仅要写解法,还需要写测试用例,挺有趣的。 原题来自
https://me.guanghechen.com/post/quiz/partition/find-duplicate-number/#footnote-2
PS: 这个博客效果是炫,太费 CPU了,我还是喜欢简洁些的
题目
给定一个长度为 N+1 的数列 A={a0,a1,a2⋯,aN}. 其中 1⩽ai⩽N,0⩽i⩽N,N⩾1.
在满足以下限制的前提下找出数组中任意一个重复的数字:不能修改输入的数组;只能使用 O(1) 的额外空间
这个题目的条件是精心挑选的,我理解为是为了算法而特意设定的约束条件。比方说
- 数组长度说 N + 1,而不是 N
- 数组里不包含 0
- 找到任意一个,而不是全部
所以简化下理解,数组里包含里一个从 1 ~ N 的自然序列,额外再加一个 1 ~ N 的数字。
解法
如果没有最后一个约束条件,我们有一个暴力算法,通常我不会做的题目我都会先去掉一些条件来看最差的实现方式,从而理解题目里的考察点。
暴力算法
其一、不借助额外的空间,符合题目描述,但是时间复杂度高 o(n^2)
|
|
这个算法的优点是没有使用额外空间;而且稍微改造下就可以找到所有的重复数组。
其二,借助一个辅助空间,空间复杂度 o(n)
.
主要思路是,初始化一个 map 遍历一遍数组,然后以 value 为 key,遍历到某个元素时,先检查 map 是否存在此 value,有则说明已经重复了,没有把本 value 保存到 map。
|
|
以上是我自己脑瓜子能想出来的,下面就是算法网站的标准答案,我按照自己的理解加上注释,以为能变成我自己的理解,后面可以举一反三。
分治法
分治法说起来简单,实际上本题目能用分治有以下的前提条件
- 题目数量 N+1,保证了所有数字都应该出现,而且有一个额外的数字和现有的数字重合。所以可以使用鸽笼原理
- 不需要找到所有重复的
主要的思路是:
- 当所有值都存在于 1 ~ n 时,如果没有重复,则 小于等于 n 值的数量一定和 n 值本身是一致的;
- 如果 小于等于 n 值的数量大于 n 本身,说明其中有一个被多算了一次;
- 如果少,则说明在另外一个区间,而且另外一个区间不包括 n;
- 所以本算法的目的是去找具体的值,而不是去找下标,不要习惯性的去找下标。这点很重要,因为通常数组的算法都是去操作下标
|
|
带环链表
想到数组里带重复的项目,可以理解为链表里带有环,而本题目转化为如何求解环,已经如何确认环的入口位置。
但我们不能直接像其他文章里那样直接进行第二步——环监测
,第三部——环的定位
。我们需要确认如何把数组转化为链表。
第一步,把数组转化为链接
我在这一步里走来弯路,最开始的想法是数组第一个作为 head,它的值指向下一个下标,但是考虑到数组里没有 0,所以对每个值都做里-1
操作。以下是错误示范
|
|
上面代码是为了演示如何把数组当列表遍历,但是遇到 [1, 2, 5, 4, 3, 2, 6, 7]
,这样测试用例的时候出现死循环,nums[1-1]=1
。然后我就换思路里,变成了数组的相邻当作指针的指向,每一步都检查 val 是否存在(不贴代码了)。其实大部分的思路是对的,就是不应该对 x - 1;还有不应该顾虑
|
|
这样的死循环。它其实是一种合理的终止条件,那说明一定有一个项指向了 x,即
|
|
意味着 y=x
。而初始条件 nums[0]
绝对不会 = 0
,意味着不会 x = 0,即
|
|
不会在第一步就产生死循环,一定会至少一次的指针跳转。而且因为 ai > 0,所以不会再次访问数组第一项。
|
|
证明
虽然得知了结果,但是现在需要证明的是
- 为何用快慢指针终究会相遇;
- 如何确保定位到环之后,从新出发的指针能和老指针回合在
在纸上证明了(还在学如何在 html 里载入图示,待补充)。f 表示快指针、 s 表示慢指针、q 是直线部分,p 是环大小
- 快慢指针一定会相遇。 则当 s 进入环入口时, $$ d = f - s = 2p - q / p - q$$ 那么还需要 d 步两个指针就能相遇(意味着 value 相同)
- 如何确认环的入口 q > p 时,以慢指针进入环后相遇的位置为 2p - q,那么说明此时还需要再走 q 才算环都闭合。如果此时,第二个慢指针走 p 步,恰好相遇; q < p 时,同理还更简单。
变形题目求解
给定一个长度为 N+1 的数列 A={a0,a1,a2⋯,aN}. 其中 1⩽ai⩽N,0⩽i⩽N,N⩾1.
在满足以下限制的前提下找出数组中任意一个重复的数字:不能修改输入的数组;只能使用 O(1) 的额外空间
额外条件,只有一个数字是重复,如何求解?
测试代码的代码
(跑测试集合)
|
|
这个题目陆陆续续我大概做了 2 天,其实还是学了很多。
参考
EOF
Author hite
LastMod 2021-08-10