【LetMeFly】3289.数字小镇中的捣蛋鬼:哈希表O(n)空间 / 位运算O(1)空间
力扣题目链接:https://leetcode.cn/problems/the-two-sneaky-numbers-of-digitville/
数字小镇 Digitville 中,存在一个数字列表 nums,其中包含从 0 到 n - 1 的整数。每个数字本应 只出现一次 ,然而,有 两个顽皮的数字额外多出现了一次,使得列表变得比正常情况下更长。
为了恢复 Digitville 的和平,作为小镇中的名侦探,请你找出这两个顽皮的数字。
返回一个长度为 2 的数组,包含这两个数字(顺序任意)。
示例 1:
输入: nums = [0,1,1,0]
输出: [0,1]
解释:
数字 0 和 1 分别在数组中出现了两次。
示例 2:
输入: nums = [0,3,2,1,3,2]
输出: [2,3]
解释:
数字 2 和 3 分别在数组中出现了两次。
示例 3:
输入: nums = [7,1,5,4,3,4,6,0,9,5,8,2]
输出: [4,5]
解释:
数字 4 和 5 分别在数组中出现了两次。
提示:
2 <= n <= 100nums.length == n + 20 <= nums[i] < n- 输入保证
nums中恰好包含两个重复的元素。
解题方法一:哈希表
使用哈希表统计每个数是否出现过。
遍历数组,若一个数已经在哈希表中出现过,则将其加入到答案中;否则将这个数添加到哈希表中。
- 时间复杂度 O ( n ) O(n) O(n)
- 空间复杂度 O ( n ) O(n) O(n)
AC代码
C++
cpp
/*
* @LastEditTime: 2025-10-31 22:19:51
*/
class Solution {
public:
vector<int> getSneakyNumbers(vector<int>& nums) {
unordered_set<int> se;
vector<int> ans;
for (int t : nums) {
if (se.count(t)) {
ans.push_back(t);
} else {
se.insert(t);
}
}
return ans;
}
};
Python
python
'''
LastEditTime: 2025-10-31 22:21:51
'''
from typing import List
class Solution:
def getSneakyNumbers(self, nums: List[int]) -> List[int]:
se = set()
ans = []
for t in nums:
if t in se:
ans.append(t)
else:
se.add(t)
return ans
Java
java
/*
* @LastEditTime: 2025-10-31 22:27:27
*/
import java.util.Set;
import java.util.HashSet;
class Solution {
public int[] getSneakyNumbers(int[] nums) {
int[] ans = new int[2];
int already = 0;
Set<Integer> se = new HashSet<>();
for (int t : nums) {
if (se.contains(t)) {
ans[already++] = t;
} else {
se.add(t);
}
}
return ans;
}
}
Go
go
/*
* @LastEditTime: 2025-10-31 22:24:27
*/
package main
func getSneakyNumbers(nums []int) (ans []int) {
se := map[int]struct{}{}
for _, t := range nums {
if _, ok := se[t]; ok {
ans = append(ans, t)
} else {
se[t] = struct{}{}
}
}
return
}
Rust
rust
/*
* @LastEditTime: 2025-10-31 22:39:35
*/
use std::collections::HashSet;
impl Solution {
pub fn get_sneaky_numbers(nums: Vec<i32>) -> Vec<i32> {
let mut se = HashSet::new();
let mut ans = Vec::with_capacity(2);
for t in nums {
if se.contains(&t) {
ans.push(t);
} else {
se.insert(t);
}
}
ans
}
}
解题方法二:位运算
需要用到位运算两个性质:
- 异或一个数偶数次,相当于什么都没干
- lowbit: x & − x x\&-x x&−x得到的结果是 x x x二进制下最后一个 1 1 1及其后的 0 0 0
知道了这两个性质,我们就可以先计算出 x ⊕ y x\oplus y x⊕y(假设 x x x和 y y y出现了两次)
使用 0 0 0来异或数组中的每一个数然后再从 0 0 0异或到 n − 1 n-1 n−1,那么数组中每个出现了一次的数总计被异或两次,相当于没有被异或;
而 x x x和 y y y总计被异或了3次,也相当于分别被异或了一次。
所以得到的最终结果就是 x ⊕ y x\oplus y x⊕y的值。
接下来拆分出 x x x和 y y y,核心思想是把 x x x和 y y y分成两组。怎么分成两组呢?还记得我们刚刚得到的 x ⊕ y x\oplus y x⊕y吗:
x ≠ y x\neq y x=y所以 x ⊕ y x\oplus y x⊕y一定非零。假设 x ⊕ y = a x\oplus y=a x⊕y=a,那么对于 a a a二进制下非零的位, x x x和 y y y的对应位一定一个为零一个非零。
为了方便,我们可以以 l o w b i t lowbit lowbit为依据,将所有的数分成两组。 0 0 0到 n − 1 n-1 n−1中所有这一位为 1 1 1的数分成一组,所有这一位为 0 0 0的数分成一组。
那么对于两组分别进行类似的运算(异或两次),就能分别得到 x x x和 y y y了。(因为 x x x所在的那一组里只有 x x x出现了两次)
- 时间复杂度 O ( n ) O(n) O(n)
- 空间复杂度 O ( 1 ) O(1) O(1)
AC代码
C++
cpp
/*
* @LastEditTime: 2025-10-31 22:49:15
*/
class Solution {
public:
vector<int> getSneakyNumbers(vector<int>& nums) {
int xored = 0;
for (int t : nums) {
xored ^= t;
}
for (int i = 0; i < nums.size() - 2; i++) {
xored ^= i;
}
// now xored is x^y
int lowbit = xored & -xored;
int ans1 = 0, ans2 = 0;
for (int t : nums) {
if (t & lowbit) {
ans1 ^= t;
} else {
ans2 ^= t;
}
}
for (int i = 0; i < nums.size() - 2; i++) {
if (i & lowbit) {
ans1 ^= i;
} else {
ans2 ^= i;
}
}
return {ans1, ans2};
}
};
同步发文于CSDN和我的个人博客,原创不易,转载经作者同意后请附上原文链接哦~
千篇源码题解已开源