剑指 Offer 56 - II. 数组中数字出现的次数 II
在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
示例 1:
输入:nums = [3,4,3,3]
输出:4
示例 2:
输入:nums = [9,1,7,9,7,9,7]
输出:1
限制:
1 <= nums.length <= 10000
1 <= nums[i] < 231
解题思路
大佬的解题思路是三进制,也就是用两位来模拟出现三次的情况,一旦三次了就置为0,这样最后只剩下的数即为只出现一次的数。
为了更清楚地分析,我们可以列出一个真值表
two_ | one_ | input(num) | ------> | two | one |
---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | |
0 | 1 | 0 | 0 | 1 | |
1 | 0 | 0 | 1 | 0 | |
0 | 0 | 1 | 0 | 1 | |
0 | 1 | 1 | 1 | 0 | |
1 | 0 | 1 | 0 | 0 |
其中 two_和one_的组合只会有00、01、10三种情况,num有两种情况,因此总共是6种情况,分别计算对应的结果two、one。
由真值表可得:
two = (two_)(!one_)(!num) + (!two_)(one_)num
one = (!two_)(one_)(!num) + (!two_)(!one_)num
one可化简为:
one = (!two_)(one_ ⊕ num)
此时我们其实已经可以写出初步的代码了
class Solution {
public int singleNumber(int[] nums) {
int ones = 0, twos = 0;
for(int num : nums){
//因为下一步ones就要更新为新值
//这里我们用一个临时变量tmp保存ones
int tmp = ones;
ones = ones ^ num & ~twos;
twos = twos & ~tmp & ~num | ~twos & tmp & num;
}
return ones;
}
}
还可以更简洁吗?我们可以试一试。
还记得刚才的真值表吗?如果是one_先更新变成了one,此时two还没有计算,那如果这个时候用新的one来代替旧的one_可以吗?
答案是肯定的,这个时候我们只去看two_到two的变化就可以了
two_ | 更新后的one | input(num) | ------> | two |
---|---|---|---|---|
0 | 0 | 0 | 0 | |
0 | 1 | 0 | 0 | |
1 | 0 | 0 | 1 | |
0 | 1 | 1 | 0 | |
0 | 0 | 1 | 1 | |
1 | 0 | 1 | 0 |
可以得到:
two = (two_)(!one)(!num) + (!two_)(!one)num
//提出共同的(!one) 发现剩下的也是一组异或
two = (!one)(two_ ⊕ num)
到这里,答案就呼之欲出了
------------------------------最终答案在这里↓-------------------------------------
class Solution {
public int singleNumber(int[] nums) {
int ones = 0, twos = 0;
for(int num : nums){
ones = ones ^ num & ~twos;
twos = twos ^ num & ~ones;
}
return ones;
}
}
-----------------------------最终答案在这里↑------------------------------------
到此结束! 诶,还没结束呢, 那为什么不能用two来表示one呢?
如果你想,可以试试。但这个时候你会发现,前后的two_和two 在输出one为1的行内,是完全一致的,
也就是说你想用two来代替one表达式中的two_,结果只是单纯把变量换个名字而已,其他的没区别。。。
one = (!two_)(one_)(!num) + (!two_)(!one_)num
one = (!two_)(one_ ⊕ num)
||
v
one = (!two)(one_)(!num) + (!two)(!one_)num
one = (!two)(one_ ⊕ num)
体现在代码上就是这样的:
class Solution {
public int singleNumber(int[] nums) {
int ones = 0, twos = 0;
for(int num : nums){
int tmp = twos;
twos = twos & ~ones & ~num | ~twos & ones & num;
ones = ones ^ num & ~tmp;
}
return ones;
}
}
ok到此结束,一口气拉到底的码友们,这个不是最简洁的答案,第二个代码块才是。。。
后记
大概是我比较笨,看题解啊什么的,代码直接甩出来我也不太懂,导致的结果就是这道题明明做过,但是隔一段时间之后再让我去做,可能关于最佳解法的思路还是不通透,说白了就是都是靠背而不是靠理解的。自己这么过一遍之后可以说是理解了,膜拜一下大佬的思路。另外这篇博客也给了我很大的启发,也谢谢这位码友。
记录一下 备忘