力扣剑指56:数组中数字出现的次数C++位运算之触类旁通
Posted The Gao
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了力扣剑指56:数组中数字出现的次数C++位运算之触类旁通相关的知识,希望对你有一定的参考价值。
剑指56-I
题目重现
一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
分析
如果对空间复杂度没有要求的话,开辟一个unordered_map是一个很好的选择。这样的话解决思路就是,遍历两次,第一次遍历目的是填充unordered_map的key值和value值,第二次遍历目的是查找哈希表中value=2的key值。
在此题中,对时间复杂度的要求是O(n),因此不能进行嵌套循环。对空间复杂度的要求是O(1),因此不能开辟额外的数组、vector容器及哈希表等存储空间,只能申请结点或者变量。
在这样的条件下,考虑使用位运算。首先复习一下位运算相关知识。
在本题中,nums中的数字可以分为两类,一类是出现次数为2的数字,一类是出现次数为1的数字。我们知道的结论是,如果一个数字与它本身作异或运算,则相当于把这个数字置0,因为该数字的每一位都与本身是一样的,所以每一位在异或运算后,都变成了0。
如果本题中,只有出现次数为1的数字只有一个,那么就设置一个变量,初始值为0【必须是0,可以思考一下为什么】,对nums中的所有元素都进行按位异或运算。因为上文中我们分析到,每个元素与它本身的元素异或后变成了0,所以所有元素异或的结果其实就是出现次数为1的那个元素。
本题中,出现次数为1的数字有两个,通过上述思路,只能得到这两个数字异或后的结果。因此我们采用分组的思想,分成什么样的两组呢?需要满足以下两点:
出现次数为1的两个数字在不同组;
出现次数为2的一组数字在相同组。
这样的话对两个组分别进行异或运算,就可以得到这两个数字了。那么,如何进行分组才能实现以上的效果呢?
现在我们通过一个变量res记录了两个出现次数为1的数字的异或结果,我们想一下,这个res从右往左第一个1是如何出现的?因为异或运算法则是对于某一位来说相同则0,不同则1,因此我们只要找到res中从右往左第一个1,这就是两个次数为1的数字的区别。
这样的分组依据能保证上述第二点吗?当然可以了。因为相同的数字,每一位也是相同的,一定会分到同一组。
代码
class Solution {
public:
vector<int> singleNumbers(vector<int>& nums) {
int res=0,a=0,b=0;
vector<int>ans;
for(auto i:nums){
res^=i; //得到两个出现次数为1的数字的异或结果
}
int div=1;
while((div&res)==0) div<<=1; //得到res中第一个非0的数位
for(auto i:nums){ //按从右到左第一个非0数位分组
if(div&i) a^=i;
else b^=i;
}
ans.push_back(a);
ans.push_back(b);
return ans;
}
};
剑指56-II
题目重现
在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
分析
本题中,没有对时间复杂度和空间复杂度有要求,因此我们可以采用哈希表的方法来做。
开辟一个unordered_map,遍历两次,第一次遍历目的是填充unordered_map的key值和value值,第二次遍历目的是查找哈希表中value=1的key值。
代码
class Solution {
public:
int singleNumber(vector<int>& nums) {
unordered_map<int,int>mp;
for(auto i:nums){
mp[i]++; //填充unordered_map
}
unordered_map<int,int>::iterator iter=mp.begin();
for(;iter!=mp.end();iter++){
if(iter->second==1)break; //二次遍历得到结果
}
return iter->first;
}
};
以上是关于力扣剑指56:数组中数字出现的次数C++位运算之触类旁通的主要内容,如果未能解决你的问题,请参考以下文章
剑指Offer56 - I. 数组中数字出现的次数(位运算)
剑指offer位运算56 - II. 数组中数字出现的次数 II