-
题目:剑指Offer56-Ⅰ.数组中数字出现次数
一个数组仅有两个出现一次的数字,其它数字均出现两次;
数组长度 >= 2(意思是说一定存在这样的两个目标数); -
思路:
1.排序+遍历:时间O(nlogn),最坏O(n ^ 2),空间O(logn),最坏O(n);
class Solution {
public:
vector<int> singleNumbers(vector<int>& nums) {
int n = nums.size();
vector<int> res;
sort(nums.begin(), nums.end());
for (int i = 0; i < n - 1; ++i) {
//下面要出现i+1,因此i最多只能取到n-2
if (nums[i] == nums[i + 1]) ++i;//如果相同,就把这两个相同的数跳过
else res.push_back(nums[i]); //否则说明当前数出现了一次,然后接下来去判断下一个
}
if (res.size() == 1) res.push_back(nums[n - 1]);//走到这里,说明第二个目标数在最后一步被跳过了,例如1,1,2,3,3,10
return res;
}
};
2.w姨的二分法:空间O(nlog(maxVal - minVal)):基本接近O(nlogn)了,但对值二分的思路还是值得借鉴的,空间O(1)
class Solution {
public:
vector<int> singleNumbers(vector<int>& nums) {
int ab = 0;
int minVal = INT_MAX, maxVal = INT_MIN, zeroCount = 0;
for (auto x : nums) {
ab ^= x;//全部数异或的结果,也就是那两个目标数异或的结果
minVal = min(minVal, x);//最小值
maxVal = max(maxVal, x);//最大值
if (x == 0) zeroCount++;
}
if (zeroCount == 1) return {
ab, 0};//排除右一个0的情况,方便之后出现0,一定是两个相同数异或得到的
int l = minVal, r = maxVal;//对值二分,而不是对下标二分
while (l <= r) {
int mid = l < 0 ? l + (l + r) / 2 : l + (r - l) / 2;
int a = 0, b = 0;//a是所有左半边数的异或结果,b是右半边
for (auto x : nums) {
if (x <= mid) a ^= x;
else b ^= x;
}
if (a != 0 && b !=- 0) return {
a, b};//说明两个目标数分别落在了<=mid和>mid两个区间内,这样两组的异或结果就分别是目标数
else if (a == 0) l = mid + 1;//两个目标数都在右区间
else r = mid - 1;//两个目标数都在左区间
}
return {
};//如果数组一定存在这样的两个目标数的话, 就不会走到这个分支;
}
};
3.(最优解)分组异或:时间O(n):遍历两次,空间O(1)
第一次遍历:将所有数异或得到ab,ab就是那两个目标数的亦或结果,由于这俩数不同,因此ab一定不为0。找到ab的二进制任意为1的位mask(之所以该位为1,就是因为这俩目标数的该位不同),为了方便直接找二进制为1的最低位;
第二次遍历:借助这点把数组分成两组,主要目的就是为了把这俩数分开,单由于其它数均出现两次,那么相同数的mask位一定相同,因此定会分在同一组。两组各自异或,各自得到一个目标数a,b;
class Solution {
public:
vector<int> singleNumbers(vector<int>& nums) {
int ab = 0, a = 0, b = 0;
for (auto x : nums) ab ^= x;//得到两个目标数异或的结果
int mask = 1;
while ((mask & ab) == 0) mask <<= 1;//循环寻找ab的二进制的最低位为1
// int mask = (ab & (-ab));//寻找ab的二进制的最低位为1的最优解
//按mask位是否为1分成两组,主要目的是把只出现一次的两个数字分开
//出现两次的数字的mask位一定相同,因此一定能分到同一组
//两组各自异或,最后各自得到只出现一次的数字
for (auto x : nums) {
if (x & mask) a ^= x;
else b ^= x;
}
return {
a, b};
}
};
//y总写法
class Solution {
public:
vector<int> findNumsAppearOnce(vector<int>& nums) {
int sum = 0;
for(auto x : nums) sum ^=x;//两目标数异或
int k = 0;
while(!(sum>>k & 1))k++;//按第k位分组
int first = 0;
for(auto x: nums)
if(x>>k&1)
first ^= x;//只需要算一组;
return vector<int>{
first, sum^first};//a ^ (a ^ b) = b;
}
};
- 总结:
两个相同数异或为0,与0异或不变;
x ^ (x-1):将x二进制为1的最低位去掉
x ^ (-x):得到x二进制为1的最低位
int mask = 1;
while ((x & (mask)) == 0) mask <<= 1; //得到x二进制为1的最低位
只要存在单调性,就能二分!