题目描述

✅ 剑指 Offer 56 - I. 数组中数字出现的次数

image-20261001230752583

image-20260928203636283

题意分析

数组中恰好两个不同的数只出现一次,其余数都出现两次。返回这两个数,顺序不限;题目要求时间复杂度为 $O(n)$、额外空间为 $O(1)$,不能靠哈希表统计次数。

解法:异或分组

核心思路

[!blue]

异或满足 x ^ x = 0、x ^ 0 = x,并且可以交换顺序。把所有元素异或,成对出现的数都会抵消,只剩两个答案的异或 xor = a ^ b。它还不能直接给出 a 和 b,但能告诉我们它们在哪些位不同。

两个答案不同,所以 xor != 0,至少有一位为 1。选其中一位作为标记,把原数组按这一位是 0 还是 1 分组:两个答案一定分开,而两次出现的相同数字一定留在同组。于是每组都只剩一个单次出现的数,分别异或即可得到两个答案。

代码用 lowbit = xor & -xor 选最低的那一位 1。补码取负相当于按位取反再加 1,最低的 1 被保留,它下方仍为 0,上方与原数互补,因此按位与后只剩这个标记位。

解题步骤

  1. 第一遍遍历数组,将所有元素异或到 xor 中。
  2. 计算 lowbit = xor & -xor,初始化两个异或累加值 a = 0、b = 0。
  3. 第二遍遍历,若 (num & lowbit) == 0,异或到 a;否则异或到 b。
  4. 两组中的成对元素抵消后,返回 a 和 b。

代码实现

class Solution {
    // 取 xor 的最低位 1 作为分组标记,将数组分成两组。
    public int[] singleNumbers(int[] nums) {
        int xor = 0;

        for (int num : nums) {
            xor ^= num;
        }

        // 选取两个答案不同的一位,保证它们进入不同组。
        int lowbit = xor & -xor;
        int a = 0;
        int b = 0;

        for (int num : nums) {
            // 相同数必然同组,在各自组内成对抵消。
            if ((num & lowbit) == 0) {
                a ^= num;
            } else {
                b ^= num;
            }
        }

        return new int[] {
            a,
            b
        };
    }
}
func singleNumbers(nums []int) []int {
    // 取 xor 的最低位 1 作为分组标记,将数组分成两组。
    xor := 0
    for _, num := range nums {
        xor ^= num
    }

    // 选取两个答案不同的一位,保证它们进入不同组。
    lowbit := xor & -xor
    a, b := 0, 0

    for _, num := range nums {
        // 相同数必然同组,在各自组内成对抵消。
        if num&lowbit == 0 {
            a ^= num
        } else {
            b ^= num
        }
    }

    return []int{
        a,
        b,
    }
}

复杂度分析

  • 时间复杂度:$O(n)$,其中 n 为数组长度;遍历数组两次。
  • 空间复杂度:$O(1)$,只保存若干整数,不实际创建两个分组数组。

关键点总结

[!green]

  • 最低置位只是方便选择,任意异或结果中的置位都能分开答案。
  • 分组同时满足相同数不分开、两个答案被分开。

易错点总结

[!yellow]

  • 检测标记位要判断 (num & lowbit) != 0,不能要求结果等于 1,标记位不一定是最低第 0 位。
  • xor & (xor - 1) 会清除最低的 1,并不是提取它;提取应使用 xor & -xor。
  • 分组依据必须是两个答案不同的那一位,随意选一位可能把两个答案分到同组。

相似题目

题目 难度 关联与区别
136. 只出现一次的数字 简单 本题先根据两个单次值的异或差异位分组,再在每组复用原题的异或抵消。
137. 只出现一次的数字 II 中等 同样处理少量例外值,原题其他值出现三次,本题其他值出现两次但例外有两个。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2021/51036818
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!