給定整數數組 nums,除兩個數字只出現一次外,其餘數字均出現兩次。找出這兩個只出現一次的數字。要求時間 O(n)、空間 O(1),順序不限。
a ^ a = 0a ^ 0 = aa ^ b = b ^ a (交換律)(a ^ b) ^ a = b (結合律)若將所有數字異或,成對數字抵消為 0,最終結果 xor_all = a ^ b (a、b 為目標數)。
xor_all 的二進制中至少有一位為 1(因 a ≠ b),該位標誌著 a、b 在此位不同(一為 0,一為 1)。
取 xor_all 的最低位 1 作為分組標誌 mask = xor_all & (-xor_all):
每組內部再次異或,成對數字抵消,分別得到 a 和 b。
int* singleNumber(int* nums, int numsSize, int* returnSize) {
int *res = malloc(sizeof(int) * 2);
int xor_all = 0;
for (int i = 0; i < numsSize; i++) xor_all ^= nums[i];
int mask = 1;
while ((mask & xor_all) == 0) mask <<= 1; // 找最低位 1
int a = 0;
for (int i = 0; i < numsSize; i++)
if (nums[i] & mask) a ^= nums[i];
res[0] = a;
res[1] = a ^ xor_all;
*returnSize = 2;
return res;
}
int* singleNumber(int* nums, int numsSize, int* returnSize) {
int xor_all = 0;
int *end = nums + numsSize;
int *p = nums;
// 1. 異或累積 (指針遍歷)
while (p < end) xor_all ^= *p++;
// 2. 取最低位 1 技巧: x & -x
int mask = xor_all & -xor_all;
// 3. 逆向遍歷分組異或
int a = 0;
while (p > nums) {
int val = *--p;
if (val & mask) a ^= val;
}
// 4. 直接復用輸入數組前兩位存結果 (省 malloc)
nums[0] = a;
nums[1] = a ^ xor_all;
*returnSize = 2;
return nums;
}
| 技巧 | 代碼 | 原理 |
|---|---|---|
| 指針遍歷替代下標 | while(p | 減少索引計算,編譯器易優化為寄存器操作 |
| 最低位 1 提取 | mask = xor_all & -xor_all | 利用補碼特性:-x = ~x + 1,僅保留最低位 1 |
| 逆向遍歷復用指針 | while(p>nums) { val = *--p; } | 無需額外變量,緊湊高效 |
| 輸入數組作輸出緩衝 | nums[0]=a; nums[1]=a^xor_all; | 省去 malloc/free,符合 LeetCode 允許修改輸入的隱性規則 |
Input: [1,2,1,3,2,5] xor_all = 3^5 = 6 (110) mask = 6 & -6 = 2 (010) Group1 (bit1=1): 2,3,2 → a=3 Group2 (bit1=0): 1,1,5 → b=5 Output: [3,5] or [5,3]
本題展示了位運算分組思想的經典應用:利用異或消除成對干擾,利用差異位分組征服。優化版通過指針算術、補碼技巧、輸入復用,將常數因子壓至極致,是嵌入式開發、高性能庫開發中「極簡風格」的典範。