137. 只出现一次的数字 II
1.题目介绍
2.题解
2.1 哈希表
思路
同本系列题I,不过多赘述
代码
class Solution {
public:
int singleNumber(std::vector<int>& nums) {
std::unordered_map<int,int> map;
for (int num:nums){
map[num]++;
}
for (auto pair:map){
if (pair.second == 1) return pair.first;
}
return 0;
}
};
结果展示
空间代价不是很理想
2.2 依次确定每一个二进制位
思路(分治算法)
这里的思路要类比上一题中的异或运算(排除所有出现偶次的数,使用了异或运算的性质)
这里重复出现三次,是一个三的倍数,如何利用好这个性质?
1.首先思考整体求和除三或者对其求和用3求余,发现了一个问题,对总和用3求余的时候,确实能求出0,1,2,但是这里的数可能是n3 + (0,1,2),无法确定这个n?
2.我们就在想能不能消除这个n,最好把这个n拆成n次一位一位的运算就好了,那么每一位我都能确定是0,1,2(或者更少的数),之后统一再变回来。
3.我们再看看这个表达式:n3 + (0,1,2),是不是感觉很像一种类似三进制的公式;
4.换句话说,如果这里的进制位数小于3,比如像二进制,就可以直接将数组中所有元素每一位求和后对3求余,得到的结果定是在(0,1)之间取一个,是一个确定的值(即单独出现的这个数的该位),之后再通过二进制的数确定该数即可
5.可是我并不知道单独出现的这个数的位数?有限定:-2^31 <= nums[i] <= 2^31 - 1,数组中的元素都在 int(即 32 位整数)范围内
代码
class Solution {
public:
int singleNumber(std::vector<int>& nums) {
int result = 0, temp = 0, pos = 0;
for (int i = 0; i < 32; i++)
{
for (int num : nums)
{
temp += (num >> i) & 1;
}
result += (temp % 3) << i;
temp = 0;
}
return result;
}
};
结果展示
这里遍历每个二进制位使得时间代价乘上了一个logC,能否进一步优化?
2.3 数字电路设计
思路
我们在计算机组成原理中,或许学过一课:简单的加法器(半加器和全加器),
那我们是否能模仿这里面的思路使用(整体位运算)代替(第i位相加综合%3的for循环操作)呢?
答案是可以的,这也就是我们为何在该系列I中使用异或运算是最优解的原因,那里均为两个一组,正好也满足二进制的进位制。但本题是三个一组,就需要进行一定的修改了。
思考一下我们这里如何解决这个三次一进位的问题呢?
我们在I中只使用了temp一个中间变量,temp每一位(i)的值正是(0,1)中取一个,足够,那我们现在能否使用两个中间变量来表示(0,1,2)呢?可以的
请看下图
我们来分析一下对于ai,bi的公式怎么来的吧
1.对于\(a_i\)
1.当 \(a_i = 0\) 即 \(a_i',\) 只有当 \(b_ix_i\)时(同时为1),\(a_i进位为1,否则保持原值为0\)
2.当 \(a_i = 1\) 即 $a_i, $ 只有当 \(b_i'x_i'\)*(同时为0) , \(a_i进位为0,否则保持原值1\)
2.对于\(b_i\)
3.总结
当我们遍历完数组中的所有元素后,\((a_ib_i)\) 要么是 (00),表示答案的第 \(i\) 位是 0; 要么是(01),表示答案的第 \(i\) 位是 1。因此我们只需要返回 \(b\) 作为答案即可。
代码
1.pair
表达式 \(pair{(\sim a \& b \& num) | (a \& \sim b \& \sim num), \sim a \& (b ^{\wedge} num)}\) 会计算两个整数值,然后创建一个匿名 pair 对象并将这两个值传递给 pair 的构造函数
2.tie
tie相当于是一个解包pair的作用,将pair中的两个值分别赋值给a,b
相当于\(a = (\sim a \& b \& num) | (a \& \sim b \& \sim num), b = \sim a \& (b ^{\wedge} num)\)
class Solution {
public:
int singleNumber(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;
}
};
2.4 数字电路设计优化
我们发现方法三中计算 \(b\) 的规则较为简单,而 \(a\) 的规则较为麻烦,因此可以将「同时计算」改为「分别计算」,即先计算出 \(b\),再拿新的 \(b\) 值计算 \(a\)。
根据真值表可以设计出电路:\(a_i=a_i^{\prime}b_i^{\prime}x_i+a_ib_i^{\prime}x_i^{\prime}=b_i^{\prime}(a_i\oplus x_i)\)
这样就与 \(b_i\) 的电路逻辑非常类似了。最终的转换规则即为:
代码
class Solution {
public:
int singleNumber(vector<int>& nums) {
int a = 0, b = 0;
for (int num: nums) {
b = ~a & (b ^ num);
a = ~b & (a ^ num);
}
return b;
}
};
作者:力扣官方题解
链接:https://leetcode.cn/problems/single-number-ii/solutions/746993/zhi-chu-xian-yi-ci-de-shu-zi-ii-by-leetc-23t6/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。