LeetCode 442 - 数组中重复的数据


文章目录

摘要

这道题看起来很像"找重复数字"的基础题,但限制条件非常关键:

你必须在 O(n) 时间、O(1) 额外空间 内找出所有出现两次的数字。

因为 nums 的范围是 1 到 n ,数组长度也是 n ,这其实暗示了一个非常经典的技巧------"原地标记法"。

这个方法特别适合处理"范围可控且和数组下标对应"的问题。

描述

给你一个长度为 n 的数组 nums,并保证其中所有数字都在 1 到 n 的区间内。

每个数字最多出现两次,你需要找出所有 恰好出现两次 的数字。

例子:

txt 复制代码
输入:[4,3,2,7,8,2,3,1]
输出:[2,3]

要求:

  • 不能用额外的数组(O(1) 空间)
  • 必须 O(n) 时间

题解答案

核心思路是:
利用元素与下标一一对应的关系,通过"取负号"标记已经出现过的数字。

做法:

  1. 遍历数组,对每个数字 x,我们取其对应下标 abs(x) - 1
  2. 如果对应位置 nums[index] 是 正数 → 说明第一次遇到,将其置为负数(标记已出现)
  3. 如果对应位置 nums[index] 是 负数 → 第二次遇到,加入答案

整个过程无需额外空间,因为我们直接在数组本体做标记。

可运行 Demo 代码(Swift)

以下代码可以直接在 Xcode / Swift Playground 运行:

swift 复制代码
import Foundation

class Solution {
    func findDuplicates(_ nums: [Int]) -> [Int] {
        var nums = nums   // 需要可变数组
        var result: [Int] = []
        
        for i in 0..<nums.count {
            let index = abs(nums[i]) - 1   // 映射到 0-based 下标
            
            if nums[index] < 0 {
                // 第二次遇到
                result.append(index + 1)
            } else {
                // 第一次遇到,取负号标记
                nums[index] = -nums[index]
            }
        }
        
        return result
    }
}

// Demo 测试
let solution = Solution()
let tests = [
    [4,3,2,7,8,2,3,1],
    [1,1,2],
    [1],
    [2,2],
    [1,2,3,4,5]
]

for nums in tests {
    print("输入: \(nums) → 输出: \(solution.findDuplicates(nums))")
}

题解代码分析

下面我们详细拆解一下关键点。

1. 为什么可以用"取负号标记"?

因为数字范围是:

复制代码
1...n

对应下标是:

复制代码
0...n-1

所以每个数字都能唯一映射到一个下标位置。

如果 nums[x-1] 被取过一次负数,则说明 ( x ) 已经出现过一次,再次遇到同样的数时,就能判断它是重复的。

2. 为什么不会越界?

因为题目保证:

txt 复制代码
1 <= nums[i] <= n

abs(nums[i]) - 1 永远合法。

3. 为什么不会影响最终结果?

我们只要保证:

  • 修改数组值不影响算法判断(我们每次都用 abs())
  • 输出不依赖修改后的 nums

所以原地修改完全没问题。

4. 使用绝对值 abs()

因为被标记的数字已经变成负数,我们需要用 abs() 来找到它的初始值。

这是必须的,否则第二次访问时可能下标会错位。

示例测试及结果

运行 Demo 后,你会看到类似输出:

txt 复制代码
输入: [4, 3, 2, 7, 8, 2, 3, 1] → 输出: [2, 3]
输入: [1, 1, 2] → 输出: [1]
输入: [1] → 输出: []
输入: [2, 2] → 输出: [2]
输入: [1, 2, 3, 4, 5] → 输出: []

你会发现:

  • 出现两次的数成功被捕捉
  • 没有重复的数组也能正常输出空数组

时间复杂度

算法只遍历一次数组:

txt 复制代码
O(n)

不论输入大小如何,永远只做 n 次操作。

空间复杂度

除了输入数组本身(允许修改),我们只使用了:

  • 一个结果数组(输出必须)
  • 常量变量

额外空间为:

txt 复制代码
O(1)

完全符合题目要求。

总结

这道题的最佳解法就是:

利用数组下标作为"哈希表",通过取负号做出现标记。

优势:

  • 不用额外空间(O(1))
  • 单次遍历(O(n))
  • 技巧性非常强,是数组题的经典套路之一
相关推荐
做怪小疯子44 分钟前
LeetCode 热题 100——二叉树——二叉树的右视图
算法·leetcode·职场和发展
努力学算法的蒟蒻1 小时前
day25(12.5)——leetcode面试经典150
算法·leetcode·职场和发展
xlq223221 小时前
23.二叉树搜索树(下)
数据结构·c++·算法
玩具猴_wjh1 小时前
快手(安全方向)面试
安全·面试·职场和发展
lzh200409191 小时前
【数据结构】二叉搜索树
数据结构·算法
c#上位机1 小时前
halcon图像膨胀—dilation1
图像处理·算法·c#·halcon
RickyWasYoung1 小时前
【聚类算法】高维数据的聚类
算法·数据挖掘·聚类
古月居GYH1 小时前
数据结构算法——排序算法解析
数据结构·算法·排序算法
Zzzzmo_2 小时前
【Java】排序算法(思路及图解)
算法·排序算法