算法------(3)位运算

&  按位与 两个二进制数同一位都是1则为1,否则为0 得到一个新的二进制数

| 按位或 两个二进制数同一位只要有一个是1就是1,否则是0 得到一个新的二进制数

~ 按位取反 对二进制数进行按位取反,~X = -(x+1)

^ 异或 两个二进制数同一位不同为1 相同为0 得到一个新的二进制数

补充:n ^ 0 = n   n ^ n = 0   a ^ b = b ^ a     a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c;

例题:leetcode 136.只出现一次的数字算法------(3)位运算_第1张图片

由异或的性质得一个数异或其本身是0,一个数异或0是他本身。因此让0和这个数组中的每个数异或,最后的结果肯定是0^只出现一次的那个数,结果也就是只出现一次的那个数。

class Solution {
public:
    int singleNumber(vector& nums) {
        int sum = 0,s = nums.size();
        for(int i = 0;i

算法------(3)位运算_第2张图片<< 左移 a << b == a * (2^b)(2的b次方) a右移b位(后面直接扔掉)

>> 右移 a>> b == a / (2^b) a左移b位(后面补0)

n >> k & 1:查看n的第k位是1还是0

练习:(1)Acwing 801 二进制中1的个数

算法------(3)位运算_第3张图片

法1:lowbit法

lowbit法可以找出某个数的二进制表示中是1的最低位,方法是用x&(-x),由于-x又是~x+1(正数的补码是其对应的负数),而~x后,在x的最低位1后面的所有0位都会变成1,而这位1会变成0,此时再加1后,后面的1因为进位又变回了0,而这个1前面都是取反的,因此x&-x得到的结果就是从1开始后面跟对应个数个0,也就是2的n次方(n代表最低位的1在二进制数从右往左的第几位)。

因此当我们每求出一位最低位1时,将原数减去其,则减多少次便有几个1。

#include 
#include 
#include 
using namespace std;
int lowbit(int x){
    return x&(-x);
}
int cnt1(int x){
    int sum = 0;
    while(x){
        x = x - lowbit(x);
        sum++;
    }
    return sum;
}
int main()
{
    int n;
    scanf("%d", &n);
    for(int i = 0;i

法2:取该数每一位,判断是否为1。

每次&1判断该数最低位是否为1,(1除了最低位是1其他都是0,因此&1就可以看出最低位是1还是0),然后>>1

#include 
#include 
#include 
using namespace std;
int cnt1(int x){
    int sum = 0;
    while(x){
        if(x&1) sum++;
        x>>=1;
    }
    return sum;
}
int main()
{
    int n;
    scanf("%d", &n);
    for(int i = 0;i

(2):Leetcode面试题17.01 不用加号的加法算法------(3)位运算_第4张图片        这题确实比较复杂。我们思考加法时每个二进制位的可能情况:(1)1/1 则本位为0,进位(2)1/0 则本位为1,不进位(3)0/0 则本位为0,不进位。

        因此可以拆分为2步,一个是两数异或得到其每一位不进位时对应的数,也就是不进位的加法,另一个是两数与,得到的是每一位进位时对应的数,也就是只进位的加法,两个数再次进行相加(重复上面的步骤),直到不再需要进位,此时结果就是两数异或的结果。

        应该也可以手动求出两个数的补码然后进行模拟,不过比这个方法复杂太多。。

class Solution {
public:
    int add(int a, int b) {
        while(b){
            int tmp = (a&b) << 1;
            a = a^b;
            b = tmp;
        }
        return a;
    }
};

(3)Leetcode 338.比特位计数

算法------(3)位运算_第5张图片法1:动态规划。

每一个二进制数的1的个数,都是其左移一位之后的二进制数的1的个数,加上其最低位的数(如果其为1)。因此状态转移方程就是f(i) =  f(i >> 1) +  (i&1)。因为要先算出i>>1,所以要从小到大遍历。

class Solution {
public:
    vector countBits(int n) {
        vector x(n+1);
        x[0] = 0;
        for(int i = 1;i<=n;i++){
            x[i] = x[i>>1] +(i&1);
        } 
        return x;
    }
};

算法------(3)位运算_第6张图片法2:奇偶性

奇数的1的个数,是比它小1的偶数的1的个数加1,因为其末尾为1而偶数末尾为0,其他位都一样。偶数的1的个数与它左移一位的数的1的个数相同,因为其最后一位是0。

class Solution {
public:
    vector countBits(int n) {
        vector x(n+1);
        x[0] = 0;
        for(int i = 1;i<=n;i++){
            if(i%2==1){
                x[i] = x[i-1] + 1;
            }
            else{
                x[i] = x[i/2];
            }
        } 
        return x;
    }
};

算法------(3)位运算_第7张图片当然这题可以用lowbit做但是比较慢。。。

(4)Leetcode 191.位1的个数

法1.lowbit

class Solution {
public:
    int hammingWeight(uint32_t n) {
        int res = 0;
        while(n){
            n -= lowbit(n);
            res++;
        }
        return res;   
    }
    uint32_t lowbit(uint32_t x){
        return x&(-x);
    }
};

算法------(3)位运算_第8张图片法2:n&(n-1)

n&(n-1)可以让n的最低位1变成0,原理是n-1使得n的最低位1变到了其下一位,这样两数与之后这两位以及后面的所有位都是0,因此与了几次就说明有几位1。

class Solution {
public:
    int hammingWeight(uint32_t n) {
        int res = 0;
        while(n){
            n = n&(n-1); 
            res++;
        }
        return res;   
    }
};

算法------(3)位运算_第9张图片

(5)Leetcode 190.颠倒二进制位

普通方法要想出来很简单,但有非常睿智的方法,,,想了很久也没做出来。

法1:分治

        我们要颠倒一个字符串,本质上就是把这个字符串的左右两半分别颠倒,然后把第一段接到第二段的后面,因此我们可以递归地去处理颠倒的过程,而自顶向下,到最后就是把奇数位和偶数位颠倒。颠倒分两步,一步是取数,一步是拼接。我们以交换偶数位和奇数位举例。首先n>>1&M1就是把所有偶数位的数全部移到奇数位,然后取出其对应的数(并且还让他留在奇数位),此时奇数位全部是0(任何数&0还是0)。偶数位同理。而两者或得到的结果便是交换后的结果(任何数|0为其本身)。 因此把偶数位和奇数位进行了颠倒,随后再往上递归的进行处理。

class Solution {
private:
    const uint32_t M1 = 0X55555555;
    const uint32_t M2 = 0X33333333;
    const uint32_t M4 = 0x0f0f0f0f;
    const uint32_t M8 = 0X00ff00ff;
public:
    uint32_t reverseBits(uint32_t n) {
        n = n >> 1 & M1 | (n & M1) << 1;
        n = n >> 2 & M2 | (n & M2) << 2;
        n = n >> 4 & M4 | (n & M4) << 4;
        n = n >> 8 & M8 | (n & M8) << 8;
        return n >> 16 | n << 16;
    }
};

算法------(3)位运算_第10张图片

(6) Leetcode 231.2的幂

算法------(3)位运算_第11张图片

很水的位运算。但是有坑,无论哪种位运算情况都要考虑int取负的最大值时的情况(溢出)。

法(1):lowbit

有且仅有2的幂满足其二进制数只有一位1。因此用待测数(不为0)减去其lowbit,如果为0则为二次幂,否则不是。

class Solution {
public:
    int lowbit(int x){
        return x&(-x);
    }
    bool isPowerOfTwo(int n) {
        if(n==-2147483648) return false;
        return n && !(n - lowbit(n));
    }
};

算法------(3)位运算_第12张图片

 法(2):n&(n-1)

有且仅有2的幂满足其二进制数只有一位1。n&(n-1)去掉唯一一位1,因此如果n(不为0)去掉唯一一位1后为0,则n为2的幂,否则不是。

class Solution {
public:
    bool isPowerOfTwo(int n) {
        if(n==-2147483648) return false;
        return n && !(n&(n-1));
    }
};

算法------(3)位运算_第13张图片

(7)Leetcode 477 汉明距离总和

        一个需要仔细思考的题。我们知道,汉明距离就是两个数字的二进制表示有多少位不同的个数。因此要统计一个数组所有元素的汉明距离,就是要统计每一个数字的每一位上与多少个其他数不同。因此我们可以逐位考虑,假如数组中总共有n个数,c个数的第k位为1,则有n-c个数的第k位为0,则考虑该位时,这c个数中的某一个数到第k位为1的数的距离为0,到某一个第k位为0的数距离为1,而第k位为0的数有n-c个,又因为距离已经包含了两点,因此无需再考虑n-c个数的情况。因此该位上的汉明距离和为c*(n-c)。可以将数组排序后求最大数的二进制数位数,也可以 直接无脑循环29次。

class Solution {
public:
    int totalHammingDistance(vector& nums) {
        int sum = 0;
        for(int i = 0;i<30;i++){
            int l=0,r=0;
            for(auto c:nums){
                if(c>>i&1) r++;
                else l++;
            }
            sum += l*r;
        }
        return sum;
    }
};

算法------(3)位运算_第14张图片

 (8)Leetcode 693. 交替位二进制数

算法------(3)位运算_第15张图片

利用简单的异或性质。

法(1):异或

        由于左右两位都不相同且最高位必然是1,因此向右移一位后得到的数与原数字必然每一位都不相同,异或后得到的结果一定是每一位都为1。一开始我的想法是利用2的n(位数)次方-1,不过更快的方法是和这个数+1进行&运算,这样得到的结果一定是0且只有在原数每一位都是1(原数不为0)的情况下才可能是0,否则必然不可能是0。

class Solution {
public:
    bool hasAlternatingBits(int n) {
        long a = n^(n>>1);
        return (a&(a+1))==0;
    }
};

算法------(3)位运算_第16张图片

(9)Leetcode 05.01 插入

算法------(3)位运算_第17张图片法1:拆分

可以把M插入N的后的数分为三部分,N的前半部分+M+N的后半部分,前半部分可以用(N>>(j+1))<<(j+1)表示,M可以用M<

class Solution {
public:
    int insertBits(long N, int M, int i, int j) {
        int sum = 0;
        for(int a = 0;a>a&1) sum+=pow(2,a);
        }
        long x = ((N>>(j+1))<<(j+1));
        return sum +x+ (M<

算法------(3)位运算_第18张图片

法2:直接填入

        首先要考虑如何把应该填入的地方全部变为0。方法是:遍历其每一位,并且将其&~(1<

class Solution {
public:
    int insertBits(long N, int M, int i, int j) {
        for(int a = i;a<=j;a++){
            N &= ~(1<

算法------(3)位运算_第19张图片

你可能感兴趣的:(算法基础课,算法,leetcode)