1.只出现一次的数字(1)
地址我就不发出来了力扣,牛客都可以资源。
各位还记得按位操作符吗?这个题用按位异或嘎嘎板正嘎嘎牛。
- 按位与(&)有0就是0,全1才是1。
- 按位或(|)有1就是1,全0才是0。
- 按位异或(^)相同为0,相异为1。
看个例子:
也就是说只要是相同的两个数按位异或就是0,如果我把所有的数按位异或,相同的全部消去了,最后按位异或和(简称异或和)就是单身狗的值。
中间那个for循环叫--范围for(名字挺高级,也就这)
- auto:推演出范围变量的数据类型。根据推理,e的类型就是int,也就是auto相当于int
- e:是范围变量的名称,该变量在循环迭代期间接受不同数组元素的值。e相当于nums了。
2.只出现一次的数字(2)
这个比上一个有点意思,有很多意思了。
这个题再用按位异或就会寸步难走。疑惑后重复的消掉了,但是不重复的都保留下来了。
我们能找到两个有力条件
-2^31 <= nums[i] <= 2^31 - 1
,这意味着我们处理的数据都是32位的数据。- 其他元素出现三次。
既然是二进制了,那我们让每一位的是都相加,那么某一位如果是3的倍数说明单身狗在这一位为0,奇数位说明单身狗有值。
- 让二进制每一位相加
nums[]={2,2,3,2}
2=00000000000000000000000000000010 2=00000000000000000000000000000010 3=00000000000000000000000000000011 2=00000000000000000000000000000010 sum=00000000000000000000000000000041
sum的值不在遵循二进制,它就相当于我们的一个计数器。
- 再将sum进行模3,保留的就是单身狗的。
00000000000000000000000000000041%3=00000000000000000000000000000031
然后把二进制转换为十进制就是结果。
class Solution { public: int singleNumber(vector<int>& nums) { int res = 0; for (int i = 0; i < 32; ++i) { int cnt = 0; for (auto x : nums) { cnt += (x>>i)&1; } res |= (cnt%3)<<i; } return res; } };
(num>>i)&1的意思是右移第i位然后按位与1操作是只进行最后一位想加,让其他位为0(因为我们是一位一位来的)。
res |= (cnt%3)<<i;是先取模,然后一位一位左移,再一位一位相加。刚开始res是0,按位或有1就是1。
3.只出现一次的数字(3)
这个也有点意思但是意思不多,甚至不如上面那个有意思,但是这个水平高。
第一个是有一个单身狗,这个有一对单身狗,再按位异或它的异或和还是两个单身狗异或的结果,所以我们得出结论:这个题异或不行在这里我只想说不能光靠异或啊,他又不是美丽国队长。
nums=[1,2,1,3,2,5],异或后的结果是3和5的异或和。现在我咋样才能把他俩分开,就根据唯一的条件异或和把他俩分开。我们想一下第一题,只有一个单身狗,那我们也分两个小组,把这一对单身狗分开,但是我怎样分组,奇数偶数行吗?这个例子就是两个奇数。划界限?怎么划界限?也不行。
那我们想一下
0000 0011 3 ^0000 0101 5 0000 1110
按位异或的条件是啥,相同为0,相异为1,也就是说,有1的那一位,两个单身狗的那一位必定不同。第1,2,3位就是。
我们根据位数是1的来进行分组,那这俩单身狗一定在不同组,其他成对的一定分一组。
在不同组我们进行异或即可。
class Solution { public: vector<int> singleNumber(vector<int>& nums) { unsigned int target = 0; // 一对相同的数字异或为0 // 所以target为两个只出现了一次的元素的异或 for (auto n: nums) { target ^= n; } // 因为两数不同,lowbit必然不为0 // 物理意义就是两个不同的数字不同的最低的位在哪 int lowbit = target & (-target); int a1 = 0; int a2 = 0; // 重复上述的过程,但是将nums按照lowbit为1或者为0分类 // 则两个数必然被分到不同的类目;而相同的数字一定在同一个类目 // 所以按类目分别异或就可以得到两个不同的数字 for (auto n: nums) { if (n & lowbit) { a1 ^= n; } else { a2 ^= n; } } vector<int> ans; ans.push_back(a1); //把,a1,a2分别插入ans中 ans.push_back(a2); return ans; } };
target=1110,-target=0010(0001+1).
用范围for遍历,我们选定的这一位来分组,这一位是1的在一组,0的在下一组,每一组在按位异或,就能得到两个单身狗了。
4.求最大连续bit数
这个题也需要用到二进制转换这个题有点意思。
其实这个题思路很明确,就是找个计数器count记录连续数字1,max_num记录最大连续数。
这个题中也就是见到1就记录下来。但是如何将十进制转化为二进制的呢?——按位与1就可以了。
#include <iostream> #include <vector> using namespace std; int main(){ int n,max_num=0,count=0; cin>>n; while(n) { if(n&1) { //找每一位是1的。模1还是1 count++; } else { count=0; } n>>=1; } max_num=max(count,max_num); cout<<max_num; }
代码就写好了,但是有一点点小岔劈。运行不通过,
改进:
![]()
但是就这一点错误吗?
如果是负数呢?我们输入-1试一试。
![]()
为啥是运行结果过大呢?
-1的二进制是 1111 1111(1111 1110+1) ,当我们右移-1时,左边出现空缺就会补1,一直右移,一直补1,那这辈子也干不完啊。所以我们来个循环。
#include <iostream> #include <vector> using namespace std; int main(){ int n,max_num=0,count=0; cin>>n; for(int i=0;i<32;i++) { if(n&1) { count++; max_num=max(count,max_num); } else { count=0; } n>>=1; } max_num=max(count,max_num); cout<<max_num; }
这样就避免了负数的一直补1问题。
本文写的比较仓促,如有问题或错误希望各位大神指正。