classSolution: defsingleNumber(self, nums: List[int]) -> int: freq = collections.Counter(nums) ans = [num for num, occ in freq.items() if occ == 1][0] return ans
[sol1-JavaScript]
1 2 3 4 5 6 7 8 9 10 11 12 13 14
var singleNumber = function(nums) { const freq = newMap(); for (const num of nums) { freq.set(num, (freq.get(num) || 0) + 1); } let ans = 0; for (const [num, occ] of freq.entries()) { if (occ === 1) { ans = num; break; } } return ans; };
[sol1-Golang]
1 2 3 4 5 6 7 8 9 10 11 12
funcsingleNumber(nums []int)int { freq := map[int]int{} for _, v := range nums { freq[v]++ } for num, occ := range freq { if occ == 1 { return num } } return0// 不会发生,数据保证有一个元素仅出现一次 }
这样一来,对于数组中的每一个元素 x,我们使用位运算 (x >> i) & 1 得到 x 的第 i 个二进制位,并将它们相加再对 3 取余,得到的结果一定为 0 或 1,即为答案的第 i 个二进制位。
细节
需要注意的是,如果使用的语言对「有符号整数类型」和「无符号整数类型」没有区分,那么可能会得到错误的答案。这是因为「有符号整数类型」(即 int 类型)的第 31 个二进制位(即最高位)是补码意义下的符号位,对应着 -2^{31,而「无符号整数类型」由于没有符号,第 31 个二进制位对应着 2^{31。因此在某些语言(例如 Python)中需要对最高位进行特殊判断。
代码
[sol2-C++]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
classSolution { public: intsingleNumber(vector<int>& nums){ int ans = 0; for (int i = 0; i < 32; ++i) { int total = 0; for (int num: nums) { total += ((num >> i) & 1); } if (total % 3) { ans |= (1 << i); } } return ans; } };
[sol2-Java]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
classSolution { publicintsingleNumber(int[] nums) { intans=0; for (inti=0; i < 32; ++i) { inttotal=0; for (int num: nums) { total += ((num >> i) & 1); } if (total % 3 != 0) { ans |= (1 << i); } } return ans; } }
[sol2-Python3]
1 2 3 4 5 6 7 8 9 10 11 12
classSolution: defsingleNumber(self, nums: List[int]) -> int: ans = 0 for i inrange(32): total = sum((num >> i) & 1for num in nums) if total % 3: # Python 这里对于最高位需要特殊判断 if i == 31: ans -= (1 << i) else: ans |= (1 << i) return ans
[sol2-JavaScript]
1 2 3 4 5 6 7 8 9 10 11 12 13
var singleNumber = function(nums) { let ans = 0; for (let i = 0; i < 32; ++i) { let total = 0; for (const num of nums) { total += ((num >> i) & 1); } if (total % 3 != 0) { ans |= (1 << i); } } return ans; };
[sol2-Golang]
1 2 3 4 5 6 7 8 9 10 11 12 13
funcsingleNumber(nums []int)int { ans := int32(0) for i := 0; i < 32; i++ { total := int32(0) for _, num := range nums { total += int32(num) >> i & 1 } if total%3 > 0 { ans |= 1 << i } } returnint(ans) }
[sol2-C]
1 2 3 4 5 6 7 8 9 10 11 12 13
intsingleNumber(int *nums, int numsSize) { int ans = 0; for (int i = 0; i < 32; ++i) { int total = 0; for (int j = 0; j < numsSize; ++j) { total += ((nums[j] >> i) & 1); } if (total % 3) { ans |= (1u << i); } } return ans; }
\begin{cases} \texttt{a = (\textasciitilde a & b & x) | (a & \textasciitilde b & \textasciitilde x)} \ \texttt{b = \textasciitilde a & (b^ x)} \end{cases}
其中 \textasciitilde, &, |, ^ 分别表示按位非、与、或、异或运算。
当我们遍历完数组中的所有元素后,(a_i b_i) 要么是 (00),表示答案的第 i 位是 0;要么是 (01),表示答案的第 i 位是 1。因此我们只需要返回 b 作为答案即可。
细节
由于电路中的 a_i 和 b_i 是「同时」得出结果的,因此我们在计算 a 和 b 时,需要使用临时变量暂存它们之前的值,再使用转换规则进行计算。
代码
[sol3-C++]
1 2 3 4 5 6 7 8 9 10
classSolution { public: intsingleNumber(vector<int>& nums){ int a = 0, b = 0; for (int num: nums) { tie(a, b) = pair{(~a & b & num) | (a & ~b & ~num), ~a & (b ^ num)}; } return b; } };
[sol3-Java]
1 2 3 4 5 6 7 8 9 10 11
classSolution { publicintsingleNumber(int[] nums) { inta=0, b = 0; for (int num : nums) { intaNext= (~a & b & num) | (a & ~b & ~num), bNext = ~a & (b ^ num); a = aNext; b = bNext; } return b; } }
[sol3-Python3]
1 2 3 4 5 6
classSolution: defsingleNumber(self, nums: List[int]) -> int: a = b = 0 for num in nums: a, b = (~a & b & num) | (a & ~b & ~num), ~a & (b ^ num) return b
[sol3-JavaScript]
1 2 3 4 5 6 7 8 9
var singleNumber = function(nums) { let a = 0, b = 0; for (const num of nums) { const aNext = (~a & b & num) | (a & ~b & ~num), bNext = ~a & (b ^ num); a = aNext; b = bNext; } return b; };
[sol3-Golang]
1 2 3 4 5 6 7
funcsingleNumber(nums []int)int { a, b := 0, 0 for _, num := range nums { a, b = b&^a&num|a&^b&^num, (b^num)&^a } return b }
[sol3-C]
1 2 3 4 5 6 7 8 9 10
intsingleNumber(int *nums, int numsSize) { int a = 0, b = 0; for (int i = 0; i < numsSize; i++) { int tmp_a = (~a & b & nums[i]) | (a & ~b & ~nums[i]); int tmp_b = ~a & (b ^ nums[i]); a = tmp_a; b = tmp_b; } return b; }
复杂度分析
时间复杂度:O(n),其中 n 是数组的长度。
空间复杂度:O(1)。
方法四:数字电路设计优化
思路与算法
我们发现方法三中计算 b 的规则较为简单,而 a 的规则较为麻烦,因此可以将「同时计算」改为「分别计算」,即先计算出 b,再拿新的 b 值计算 a。
\begin{cases} \texttt{b = \textasciitilde a & (b^ x)} \ \texttt{a = \textasciitilde b & (a^ x)} \end{cases}
需要注意先计算 b,再计算 a。
代码
[sol4-C++]
1 2 3 4 5 6 7 8 9 10 11
classSolution { public: intsingleNumber(vector<int>& nums){ int a = 0, b = 0; for (int num: nums) { b = ~a & (b ^ num); a = ~b & (a ^ num); } return b; } };
[sol4-Java]
1 2 3 4 5 6 7 8 9 10
classSolution { publicintsingleNumber(int[] nums) { inta=0, b = 0; for (int num : nums) { b = ~a & (b ^ num); a = ~b & (a ^ num); } return b; } }
[sol4-Python3]
1 2 3 4 5 6 7
classSolution: defsingleNumber(self, nums: List[int]) -> int: a = b = 0 for num in nums: b = ~a & (b ^ num) a = ~b & (a ^ num) return b
[sol4-JavaScript]
1 2 3 4 5 6 7 8
var singleNumber = function(nums) { let a = 0, b = 0; for (const num of nums) { b = ~a & (b ^ num); a = ~b & (a ^ num); } return b; };
[sol4-Golang]
1 2 3 4 5 6 7 8
funcsingleNumber(nums []int)int { a, b := 0, 0 for _, num := range nums { b = (b ^ num) &^ a a = (a ^ num) &^ b } return b }
[sol4-C]
1 2 3 4 5 6 7 8
intsingleNumber(int *nums, int numsSize) { int a = 0, b = 0; for (int i = 0; i < numsSize; i++) { b = ~a & (b ^ nums[i]); a = ~b & (a ^ nums[i]); } return b; }