每天学习一点算法 2026/04/30
题目:寻找重复数
给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。
假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。
你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。
作者:LeetCode
链接:https://leetcode.cn/leetbook/read/top-interview-questions-hard/xwz4lj/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
-
二分查找
首先我们来分析一下,假设我们的 m 是重复数
在
1 ~ m-1范围中的数都是唯一的,那么这个范围中的任意一个数 i 一定满足:小于等于 i 的数字个数 count 是小于等于 i 的。在
m ~ n范围中的数因为存在重复数 m,那么这个范围中的任意一个数 i 一定满足:小于等于 i 的数字个数 count 是大于 i 的。有点抽象我们以
nums = [3,1,3,4,2]为例:-
m = 3 -
1 ~ 2范围内有 1 和 2 两个数他们 count 分别是 1 和 2,满足上面的规律。 -
3 ~ 4范围内有 3 和 4 两个数他们 count 分别是 4 和 5,满足上面的规律。
那么我们就可以根据这个规律在
1 ~ n范围进行二分查找,找到第一个 count 大于它自身的数字就是重复数字。typescriptfunction findDuplicate(nums: number[]): number { let left = 1, right = nums.length - 1 let res = 0 while (left <= right) { const mid = Math.floor((left + right) / 2) let count = 0 for (let i = 0; i < nums.length; i++) { if (nums[i] <= mid) count++ } if (count > mid) { right = mid - 1 res = mid } else { left = mid + 1 } } return res }; -
-
快慢指针
我们如果根据数组建立一个链表,使他的下一个节点在数组中的索引等于当前元素值。
以
nums = [1,3,4,2,2]为例:- 第一个节点值为:1
- 第二个节点值为:下标为 1 的 3
- 第三个节点值为:下标为 3 的 2
- 第四个节点值为:下标为 2 的 4
- 第五个节点值为:下标为 4 的 2
此时出现了重复的节点表示这个链表是有环的,只有存在有重复数字这样生成的链表一定就会有环,而环的入口 2 就是重复的数字。
这个问题就变成了寻找链表环的入口的问题,我们可以使用快慢指针,快指针每次移动两步,慢指针每次移动一步,根据 Floyd 判圈算法 两个指针一定会相遇。
假设环的长度是
len,从起点到环的入口步数是 a,入口到相遇位置的步数是 b,从相遇位置回到入口的步数是 c。首先
len = b + c两指针相遇时,慢指针走了
a + b步,快指针走了2(a + b)步。因为两指针相遇时,快指针一定会比慢指针多走若干圈,假设时 x 圈
那么快指针走的步数也可以表示成:
a + b + x * len于是就有了:
2(a + b) = a + b + x * lena = x * len - b = (x - 1) * len + c根据这个公式:如果两个指针一个从快指针出发一个从相遇点出发,当一个指针走了 a 步到达起点时,另外一个指针刚好也能绕 x - 1 圈后再走 c 步回到入口。
所以我们需要先利用龟兔赛跑式快慢指针到达相遇点,再移动一个指针至起始点,然后两个指针每次都只移动一步,相遇的节点就是环的入口也就是我们要找的重复数。
typescriptfunction findDuplicate(nums: number[]): number { let fast = 0, slow = 0 // 利用 Floyd 判圈移动指针到相遇点 do { fast = nums[nums[fast]] slow = nums[slow] } while (fast != slow) slow = 0 // 将慢指针移动到起始点 // 同步移动快慢指针直至相遇 while (fast != slow) { slow = nums[slow] fast = nums[fast] } return fast // 返回相遇点指针 };题目来源:力扣(LeetCode)