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))
  • 技巧性非常强,是数组题的经典套路之一
相关推荐
2401_85791829几秒前
实时数据处理中的C++应用
开发语言·c++·算法
2401_88456324几秒前
C++中的装饰器模式实战
开发语言·c++·算法
MicroTech20255 分钟前
微算法科技(NASDAQ :MLGO)抗量子区块链技术:筑牢量子时代的数字安全防线
科技·算法·区块链
Ivanqhz7 分钟前
图着色寄存器分配算法(Graph Coloring)
开发语言·javascript·python·算法·蓝桥杯·rust
Elsa️7469 分钟前
洛谷p5718 复习下快速排序和堆排序
数据结构·算法·排序算法
大包菜 cc11 分钟前
面试0000
面试·职场和发展
Frostnova丶12 分钟前
LeetCode 3567.子矩阵的最小绝对差
算法·leetcode·矩阵
夏日听雨眠13 分钟前
文件学习9
数据结构·学习·算法
华农DrLai14 分钟前
什么是自动Prompt优化?为什么需要算法来寻找最佳提示词?
人工智能·算法·llm·nlp·prompt·llama
野犬寒鸦14 分钟前
从零起步学习JVM|| 第二章:JVM基本组成及JVM内存区域详解
服务器·开发语言·后端·学习·面试·职场和发展