Swift 解锁 LeetCode 热门难题:不改数组也能找出重复数字?

发布于:2025-05-30 ⋅ 阅读:(21) ⋅ 点赞:(0)

在这里插入图片描述
在这里插入图片描述

摘要

在数组中找出唯一的重复数字,听起来像一道简单的题目,但如果你不能修改原数组、不能使用额外空间,挑战就变得很有意思了。本文通过 Swift 实现 LeetCode 第 287 题《寻找重复数》的题解,并结合实际编码场景详细拆解背后的逻辑推理过程,帮你真正掌握这道经典的算法题。

描述

题目要求你在一个包含 n + 1 个整数的数组中找到一个重复的数。这些数的取值范围是 [1, n],而且保证 只有一个数字重复,可能重复多次

但它还加了一些限制:

  • 数组不能修改(也就是只读)
  • 空间复杂度得是常数级 O(1)
  • 尽可能做到时间复杂度为线性 O(n)

看起来挺棘手?别急,下面我们一步一步来拆解。

题解答案

常见的思路有三种:

  1. 暴力法 / 哈希法(不能用,违反空间复杂度)
  2. 排序 + 遍历(不能用,违反不能修改数组)
  3. 快慢指针(可以用,经典的“链表找环”变体)

我们要用的是第三种方法,也叫 Floyd 判圈算法。

思路是把数组元素看成指针,nums[i] 代表指向下一个元素的索引,就像一个链表。如果有重复数字,就一定会有一个“环”。

题解代码分析

func findDuplicate(_ nums: [Int]) -> Int {
    var slow = nums[0]
    var fast = nums[0]

    // 第一阶段:找相遇点
    repeat {
        slow = nums[slow]
        fast = nums[nums[fast]]
    } while slow != fast

    // 第二阶段:找入口(重复的数字)
    slow = nums[0]
    while slow != fast {
        slow = nums[slow]
        fast = nums[fast]
    }

    return slow
}

解读:

  • 第一次 while 循环找的是两个指针在环里相遇的那个位置。我们先从 nums[0] 开始,slow 每次走一步,fast 每次走两步,像跑圈一样,最终肯定会在环里相遇。
  • 第二次 while 循环是经典的“找环入口”,我们让一个指针从开头出发,另一个从相遇点出发,每次走一步,再次相遇的点就是重复数字。

示例测试及结果

let test1 = [1, 3, 4, 2, 2]
print(findDuplicate(test1))  // 输出: 2

let test2 = [3, 1, 3, 4, 2]
print(findDuplicate(test2))  // 输出: 3

let test3 = [3, 3, 3, 3, 3]
print(findDuplicate(test3))  // 输出: 3

无论重复的是谁,只要有重复,这个算法就能找出来,效率也很高。

时间复杂度

  • 整体是 O(n),因为我们最多只跑两次遍历,而且每次最多走 n 步。

空间复杂度

  • O(1),只用了两个变量 slowfast,没有开任何额外空间。

总结

这题乍一看像是“简单查重”,实则考验对链表结构与指针遍历逻辑的理解。如果你碰到限制不能修改数组、不能用额外空间的情况,这种借助“指针构造隐含链表”的技巧就非常值得掌握。

而且这种技巧在很多系统设计里也很实用,比如检测日志流的循环引用、追踪用户路径中的重复操作等等。

实际场景类比

想象一下你在做用户路径分析,用户访问路径用数组表示 [页面1, 页面2, 页面3, 页面2, 页面4],你希望知道哪个页面用户又返回访问了。

你不能改动原始日志(只读),也不能复制一份数据再做处理(节省内存),这时候就可以考虑类似的“快慢指针找环”方式来识别重复跳转的路径。

可运行 Demo(Swift Playground)

import Foundation

func findDuplicate(_ nums: [Int]) -> Int {
    var slow = nums[0]
    var fast = nums[0]
    
    repeat {
        slow = nums[slow]
        fast = nums[nums[fast]]
    } while slow != fast
    
    slow = nums[0]
    while slow != fast {
        slow = nums[slow]
        fast = nums[fast]
    }
    
    return slow
}

let sample = [1, 4, 6, 2, 5, 3, 6]
print("重复数字是:\(findDuplicate(sample))")

未来展望

这个题是很多“限制场景下查重”的代表题,我们可以往更复杂的方向去探索:

  • 如何在海量数据(分布式系统)中查找重复?
  • 如果有多个重复的数字,又该如何扩展这类算法?
  • Swift 中的指针式遍历,还有哪些地方能发挥类似作用?