在问 · 算法题解

287. 寻找重复数

中等技巧快慢指针(Floyd 判环)力扣原题 ↗

题目描述

输入一个整数数组 nums,长度为 n + 1,每个元素都在 1 到 n 之间(含两端)。

数组里只有一个数是重复的,它可能出现两次,也可能出现更多次;其余的数都只出现一次。找出并返回这个重复的数。

要求不能修改 nums,并且只使用 O(1) 的额外空间。

示例 1
输入:nums = [1,3,4,2,2]
输出:2

解释:2 出现了两次。

示例 2
输入:nums = [3,1,3,4,2]
输出:3

解释:3 出现了两次。

示例 3
输入:nums = [3,3,3,3,3]
输出:3

解释:3 出现了五次。

数据范围

  • 1 <= n <= 10^5
  • nums.length == n + 1
  • 1 <= nums[i] <= n
  • 只有一个整数出现了两次或更多次,其他整数各出现一次。

进阶:能否说明 nums 中为什么一定会有重复的数?能否设计出 O(n) 时间的算法?

解题思路:快慢指针(Floyd 判环)

把 nums 当作链表 next 指针,用 Floyd 快慢指针先找环内相遇点,再从起点和相遇点同步走找环入口。

为什么正确

数组映射形成的函数图必有环,重复值正是环的入口。

复杂度分析

时间 O(n),空间 O(1)。

易错点

fast 必须执行两次索引跳转;第二阶段重置的是 slow,不是 fast。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

from typing import List

class Solution:
    def findDuplicate(self, nums: List[int]) -> int:
        slow = nums[0]
        fast = nums[0]
        while True:
            slow = nums[slow]
            fast = nums[nums[fast]]
            if slow == fast:
                break
        slow = nums[0]
        while slow != fast:
            slow = nums[slow]
            fast = nums[fast]
        return slow

载入编辑器,自己跑一遍 →

其他解法

三种方法都满足不修改数组和 O(1) 空间。值域二分和逐位统计都是“计数比较”思想,O(n log n);快慢指针把下标到值看成链表的 next 指针,重复数就是环的入口,达到 O(n)。

解法 2:在值域上二分 时间 O(n log n),空间 O(1)

设 cnt(x) 为数组中 ≤ x 的元素个数。若重复数是 t,则 x < t 时 cnt(x) ≤ x,x ≥ t 时 cnt(x) > x,具有单调性。在 [1, n] 上二分,找第一个满足 cnt(x) > x 的 x,每次计算 cnt 需要扫一遍数组。

满足不修改数组、常数空间,正确性用抽屉原理就能说清。时间多一个 log。

from typing import List

class Solution:
    def findDuplicate(self, nums: List[int]) -> int:
        lo, hi = 1, len(nums) - 1
        while lo < hi:
            mid = (lo + hi) // 2
            cnt = sum(1 for x in nums if x <= mid)
            if cnt > mid:
                hi = mid
            else:
                lo = mid + 1
        return lo

解法 3:二进制逐位统计 时间 O(n log n),空间 O(1)

对每一个二进制位 b,分别统计数组中这一位为 1 的个数 x 和 1..n 中这一位为 1 的个数 y。重复数在这一位为 1 当且仅当 x > y。把所有满足 x > y 的位拼起来就是答案。

同样不修改数组、常数空间,按位独立判断。要理解重复数出现多次、其他数缺失时这个比较仍然成立,推理略绕。

from typing import List

class Solution:
    def findDuplicate(self, nums: List[int]) -> int:
        n = len(nums) - 1
        ans = 0
        for b in range(n.bit_length()):
            mask = 1 << b
            x = sum(1 for v in nums if v & mask)
            y = sum(1 for i in range(1, n + 1) if i & mask)
            if x > y:
                ans |= mask
        return ans

技巧 专题的其他题目

查看 技巧 全部题目 →