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))
  • 技巧性非常强,是数组题的经典套路之一
相关推荐
IronMurphy6 小时前
【算法四十三】279. 完全平方数
算法
墨染天姬6 小时前
【AI】Hermes的GEPA算法
人工智能·算法
papership6 小时前
【入门级-数据结构-3、特殊树:完全二叉树的数组表示法】
数据结构·算法·链表
smj2302_796826526 小时前
解决leetcode第3911题.移除子数组元素后第k小偶数
数据结构·python·算法·leetcode
Beginner x_u7 小时前
链表专题:JS 实现原理与高频算法题总结
javascript·算法·链表
wxy不爱写代码7 小时前
C++多线程
面试·职场和发展
野生技术架构师10 小时前
金三银四面试总结篇,汇总 Java 面试突击班后的面试小册
java·面试·职场和发展
_深海凉_10 小时前
LeetCode热题100-寻找两个正序数组的中位数
算法·leetcode·职场和发展
ja哇11 小时前
大厂面试高频八股
java·面试·职场和发展
踩坑记录11 小时前
leetcode hot100 寻找两个正序数组的中位数 hard 二分查找 双指针
leetcode