题目描述

✅ LCR 004. 只出现一次的数字 II

image-20260928234633961

题意分析

数组中恰好一个值只出现一次,其他每个值都出现三次,返回这个单次出现的值。数值允许为零或负数,解法按 32 位有符号整数的补码处理。

目标是线性时间、常数额外空间。普通异或只能消去成对重复,三个相同值异或后仍保留自身,不能直接用于这道题;需要让三次重复的贡献归零。

解法:逐位计数对 3 取余

核心思路

[!blue]

把每个整数看成 32 个独立的二进制位。固定某一位时,一个出现三次的值会贡献零个或三个一,所以所有重复值在这一位的计数之和都是三的倍数。

只出现一次的目标值在这一位仅贡献零或一。因此,将本位的一的总数对三取余,就恰好得到目标值的这一位。依次求出全部余数,再把它们移回对应位置并按位或到 answer,即可还原答案。

代码用 (x >> i) & 1 提取第 i 位。即使负数采用算术右移,符号扩展也不会改变这里最后选出的原第 i 位,因此正负输入可以按同一方式统计。

第 31 位同样参与取余与拼接,才能保留负数的补码符号。Java 的 int 本身就是 32 位,直接组装即可;Go 用 int32 保存结果位模式,再转换成返回类型 int,避免在 64 位环境中把仅设置第 31 位的无符号大小误当成正数。

每一位的计数独立清零,固定处理 32 位,每位扫描整个数组。只保存当前位计数与答案,无需为每个不同数分配记录;答案为零时,所有余数都为零,初始值自然正确。

解题步骤

  1. 初始化答案为零,枚举第零位到第三十一位。
  2. 当前位的计数从零开始,扫描所有数并累加 (x >> i) & 1。
  3. 将计数对三取余,左移回第 i 位后按位或入答案。
  4. 完成全部位后返回结果,Go 按 int32 的有符号值转换。

代码实现

class Solution {
    public int singleNumber(int[] nums) {
        int answer = 0;

        // 32 位必须跑满,第 31 位是符号位,截断会算错负数答案。
        for (int i = 0; i < 32; ++i) {
            int cnt = 0;

            for (int x : nums) {
                cnt += x >> i & 1;
            }

            // 出现三次的元素在这一位上的贡献是 3 的倍数,取余后被整体抹掉。
            cnt %= 3;
            answer |= cnt << i;
        }

        return answer;
    }
}
func singleNumber(nums []int) int {
    var answer int32
    for i := 0; i < 32; i++ {
        cnt := 0
        for _, x := range nums {
            cnt += x >> i & 1
        }
        cnt %= 3
        answer |= int32(cnt) << i
    }
    return int(answer)
}

复杂度分析

  • 时间复杂度:$O(32n) = O(n)$,整数位宽固定,每位完整扫描一次数组。
  • 空间复杂度:$O(1)$,只使用当前位、当前计数和结果等固定变量。

关键点总结

[!green]

  • 重复项的贡献在每一位上都是三的倍数,模三只留下目标位。
  • 各位独立处理,不能先把整个整数相加后对三取余。
  • 符号位也按同一规则恢复,不能先取绝对值或只处理低三十一位。
  • Go 的结果类型明确为 int32,用它恢复题目要求的符号。

易错点总结

[!yellow]

  • 直接异或全部值不能抵消三次重复。
  • 忽略符号位会把负数答案还原成不相符的非负值。
  • 每一位都要重新统计,再对三取余,不能沿用上一位的计数。
  • 先将未取余的计数移回去,会把出现次数当作答案位并污染其他位。
  • Go 若直接按宽 int 组装低三十二位,需注意符号不会自动按三十二位解释。

相似题目

题目 难度 关联与区别
136. 只出现一次的数字 简单 原题其他值出现两次,可直接异或;本题出现三次,要按位统计模3或用状态机。
260. 只出现一次的数字 III 中等 同样寻找少数例外值,原题有两个单次值,可按异或差异位分组。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/95955037
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!