第一百零四天 --- 力扣137. 只出现一次的数字 II+260. 只出现一次的数字 III
题目一
思路
Hash算法
我们什么都不考虑,直接统计各个数字出现次数,然后找出现一次的,用哈希表实现,最基础,也最好想。
位运算(遍历统计)
那么,我们现在有了一个新的需求,由于某种原因,现在空间严重不足,让我们在O(1)的空间中,算出来,所以我们想到了位运算。
1、因为大部分出现了m次,目标(仅一个)出现了n次,m,n同为奇数或偶数且m>n。因为m,n特点,所以用不了异或(只有目标出现奇数次,大部分数出现偶数次,直接异或可以保留目标),所以直接十进制方法作废,那就潜入二进制一位一位的看。
2、拿{3,3,3,5}作为例子,具体如下:
<1> 通过上图,我们不难发现,比如就对于第一位,因为3连续出现3次,也就是会连续出现3个1,我们目标是保住最后的1,怎么办才能干掉连续的3个1?
<2> 很简单,把第一位全相加,出现3次的元素,相加后都是3的倍数,那么目标出现了n次(n<m)就自然成为了余数,所以%就会得到目标。
(如果n=1,那么直接%m就行,但n>1&&n<m的时候,最后得%m,后/n才行)
综上:以上方法适用于任何"大部分出现了m次,目标(仅一个)出现了n次,m,n同为奇数或偶数且m>n"问题的求解
自动机解法
(本方法效率更为高,是由上面的位运算问题演化而来,较难)
(分析办法我画图说明)
我们观察上面位运算方法,针对某一个位,每接收到一个1,状态会变 当接收到3个1后就会回到原点,所以我们可以用有限状态自动机来模拟这个过程。
1、因为int共32位,每一位都一样的算法,最后直接全部整合即可,后面的分析都是基于某一位的。
2、
以上,就是完整的分析过程。
代码
1、Hash
class Solution {
public:
int singleNumber(vector<int>& nums) {
unordered_map<int, int> item;
int ans = 0;
for (int i = 0; i < nums.size(); i++) {
item[nums[i]]++;//统计
}
for (int i = 0; i < nums.size(); i++) {
if (item[nums[i]] == 1) {//寻找
ans = nums[i];
}
}
return ans;
}
};
所有代码均以通过力扣测试
(经过多次测试最短时间为):
时间复杂度:O(N)
空间复杂度:O(N)
2、位运算
class Solution {
public:
int find_single_num(vector<int> nums, int remainder) {//传入余数
int bits[32] = {};
for (int i = 0; i < nums.size(); i++) {
for (int j = 0; j < 32; j++) {//统计各个位的和
bits[j] += nums[i] & 1;
nums[i] >>= 1;
}
}
for (int i = 0; i < 32; i++) {//取模
bits[i] %= remainder;
}
int ans = 0;
for (int i = 0; i < 32; i++) {//还原
ans |= bits[31 - i];
ans <<= 1;
}
return ans;
}
int singleNumber(vector<int>& nums) {
return find_single_num(nums, 3);
}
};
所有代码均以通过力扣测试
(经过多次测试最短时间为):
时间复杂度:O(N)
空间复杂度:O(1)
3、自动机
class Solution {
public:
int singleNumber(vector<int>& nums) {
int ones = 0, twos = 0;//分别由32个one、two组成
for (int tmp : nums) {//相当于32位并发运算
ones = ones ^ tmp&~twos;
twos = twos ^ tmp&~ones;
}
return ones;
}
};
所有代码均以通过力扣测试
(经过多次测试最短时间为):
时间复杂度:O(N)
空间复杂度:O(1)
题目二
思路:位运算
本题类型:大部分出现了m次,目标出现了n次,m为偶数,n为奇数类型!
1、先回顾 n^n=0、 0 ^ n =n.所以偶数个数相互异或为0,奇数个数相互异或得到自己。
2、所以这样的话就不用上一题中的位运算法了,有一种更为方便的方法,根据上面"2",就是把所有的数放在一起异或,非目标的直接全干掉了,只会留下目标。
3、如果目标唯一,直接得到答案。
4、像本题,有两个目标,那么就得面对一个问题,就是将两个数分离。分离就要找不同点,但是现在得到的是两个目标异或结果,怎么判断不同?
<1> 所以找异或结果中第一个是1的位,它代表了两个目标数在该位不一致,一个是0一个是1
<2> 既然知道了第k位不一致,所以再次全体异或,但是我现在知道了咋分开目标,就是第k位,一个是0,一个是1,所以是0的放在一堆,是1的放在一堆,就分开了
代码
注意:一定要手动为优先级先后加好括号,防止出错!!!!!
class Solution {
public:
vector<int> singleNumber(vector<int>& nums) {
vector<int> answers(2);
int item = 0;
for (int tmp : nums) {
item ^= tmp;
}
int ans1 = 0, ans2 = 0;
int point = 1;//寻找第几位,两个目标的二进制数不一致,即为1
while (1) {
if ((item & point) != 0) {//& 运算优先级低,一定加好括号
break;
}
point <<= 1;//继续向左走
}
for (int tmp : nums) {//分组异或,进而分开两个目标
if ((tmp & point) == 0) {
ans1 ^= tmp;
}
else {
ans2 ^= tmp;
}
}
answers[0] = ans1;
answers[1] = ans2;
return answers;
}
};
所有代码均以通过力扣测试
(经过多次测试最短时间为):
时间复杂度:O(N)
空间复杂度:O(1)