liyirui's Blog

牛客网-程序员代码面试指南-整数二进制表达式中有多少个1-CD145

难度:⭐⭐

自己解决程度:能写,但是不是最优解

这个题想写出来不难,但是想要写好很难,方法与方法之间的差别还挺大的。

我一开始的做法是这样的:

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=1000011n&(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的过程。

【总结】

除去上面的三种方法以外,书上还给出了一些“逆天”的算法,个人认为不太需要掌握,重点掌握好第一种与第二种方法就好了。

牛客网-程序员代码面试指南-不用做任何判断运算符找出两个数中的较大的值-CD143

难度:⭐⭐⭐ 自己解决程度:我认为找到了比答案中更好的方法

#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也可以解决。

牛客网-程序员代码面试指南-数值中出现了一次的数字-CD147

难度:⭐⭐

自己完成程度:没能自己完成,记得在《剑指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;
}

Leetcode-231. 2的幂

Leetcode-342. 4的幂