难度:⭐⭐
自己解决程度:能写,但是不是最优解
这个题想写出来不难,但是想要写好很难,方法与方法之间的差别还挺大的。
我一开始的做法是这样的:
int main(void){
int n;
cin >> n;
cin.get();
int ct = 0;
unsigned int un = (unsigned int) n;
while(un > 0){
if(un % 2 == 1) ct++;
un = un >> 1; // 移位运算
}
cout << ct;
return 0;
}
我注意到了用移位运算来替代除法运算,但是这还不够。
【书上方法1】
int count1(int n){
int ct = 0;
while(n > 0){
ct += n & 1;
n >>>= 1; // 符号右移
}
return ct;
}
书上的方法一,通过检查最右的bit是否为1来进行统计,又比我的取余数进了一步。
但这个方法也有问题,就是它循环的次数与1的个数无关。
【书上方法2】
int count2(int n){
int ct = 0;
while(n != 0){
n &= (n-1);
ct++;
}
return ct;
}
方法二能实现循环次数只与n的二进制表达式中1的个数有关。每做一次n &= (n-1)
操作,都从其二进制表达中删掉一个1。例如,n = 1000100
那么 n-1=1000011
, n&(n-1) = 1000000
,最右边的1便被消掉了,只要n不为0,说明二进制表达式中还有1。
【书上方法3】
int count3(int n){
int ct = 0;
while(n != 0){
n -= n & (~n + 1);
ct++;
}
return ct;
}
方法3中的操作n -= n & (~n + 1)
也是移除最右边1的过程。
【总结】
除去上面的三种方法以外,书上还给出了一些“逆天”的算法,个人认为不太需要掌握,重点掌握好第一种与第二种方法就好了。
难度:⭐⭐⭐ 自己解决程度:我认为找到了比答案中更好的方法
#include <iostream>
using namespace std;
int main(void){
int a, b;
cin >> a >> b;
cin.get();
int c = a - b;
int d = b - a;
c = c >> 31;
d = d >> 31;
int e = (c & b) | (d & a);
cout << e;
return 0;
}
如果 a > b的话,c > 0,d < 0,那么右移31位以后,c = 0(全0),d = -1(全1)。
用c & b = 全0,d & a = a,然后全0 | a = a,所以就得到了最大值a。 |
这个方法的问题是,如果 a - b 发生溢出,就容易出问题,这个时候只需要把中间的一些变量设置为long long也可以解决。
难度:⭐⭐
自己完成程度:没能自己完成,记得在《剑指Offer》做过,但是这次遇到还是不会
之前做过一道与剑指offer上相似的题目 Leetcode-剑指 Offer 56 - I. 数组中数字出现的次数。
本题的解法很巧妙,假设数组中,只出现一次的数字是a和b,方法先是对数组中所有的元素求一个异或,得到的是 $a \oplus b$ 的值。然后从这里面找到一个值为1的位置,假设可以找从低位到高位的第一个1。这个位置为1就说明了,a 与 b 的这一位,一个是1,一个是0。那么就可以将整个数组分成两组,这个位置为1的是一组,这个位置为0的是一组,而且恰好每一组里都有且仅有一个出现1次的数字。
int main(void){
// 读入数据
int n;
cin >> n;
cin.get();
vector<int> arr(n);
for(int i = 0; i < n; i++) cin >> arr[i];
// 计算
// 因为额外空间复杂度O(1)的要求,所以本题无法直接哈希
// 先按位异或
int temp = 0;
for(int a : arr) temp ^= a;
// 然后找到异或后的结果中第一个值为1的位置
int posi = 0;
while(temp != 0){
if((temp & 1) != 1){
posi++;
temp = temp >> 1;
}
else break;
}
// 然后通过这一位为1还是为0,可以将数据分成两组
// 而且这两组恰好每一组都包含一个只出现一次的数字
// 然后分别求这两个数组中的哪个数字
int filter = 1 << posi;
int n1 = 0, n2 = 0;
vector<int> zeroGroup, oneGroup;
for(int a : arr){
if((a & filter) == filter) n1 ^= a;
else n2 ^= a;
}
if(n1 > n2) cout << n2 << " " << n1;
else cout << n1 << " " << n2;
return 0;
}