【算法系列】位运算专题

試著忘記壹切 提交于 2019-12-09 21:06:45

位运算专题

目录

位运算专题

LeetCode 231 2的幂

1、分析

2、代码

LeetCode 762 二进制表示中质数个数计算置位

1、分析

LeetCode 136 只出现一次的数

1、分析

2、 代码

LeetCode 476 数字的补数

1、分析

2、代码

 LeetCode 137 只出现一次的数字II

1、分析

2、代码

LeetCode 260 只出现一次的数字III

1、分析

2、代码

LeetCode 371 两整数之和

1、分析

2、代码

LeetCode 201 数字范围按位与

1、分析

2、代码

LeetCode 421 数组中两个数的最大异或值

1、分析

2、代码

 LeetCode 477 汉明距离总和

1、分析

2、代码

LeetCode 231 2的幂

题目:https://leetcode-cn.com/problems/power-of-two/submissions/

给定一个整数,编写一个函数来判断它是否是 2 的幂次方。

示例 1:

输入: 1
输出: true
解释: 20 = 1

1、分析

       如果我们用2进制来表示n,则如果是2的幂次方,那么它的最高位一定是1,且只有这一个1,在位运算中,有一个操作是n&-n表示n从低位数起的第一个1的位置k,最后值为2^k,例如n=6,二进制为110,则-n是n的反码加1,即001+1=010,再和n相与结果为010,所以如果是一个2的幂次方数例如8,二进制为1000,-n=0111+1=1000,最后结果正好等于本身n,所以我们可以判断(n&-n)==n?即可判断是否是2的幂次方数。

2、代码

class Solution {
public:
    bool isPowerOfTwo(int n) {
        return (n>0)&&((n&-n)==n);
    }
};

LeetCode 762 二进制表示中质数个数计算置位

题目:https://leetcode-cn.com/problems/prime-number-of-set-bits-in-binary-representation/

给定两个整数 L 和 R ,找到闭区间 [L, R] 范围内,计算置位位数为质数的整数个数。

(注意,计算置位代表二进制表示中1的个数。例如 21 的二进制表示 10101 有 3 个计算置位。还有,1 不是质数。)

示例 1:

输入: L = 6, R = 10
输出: 4
解释:
6 -> 110 (2 个计算置位,2 是质数)
7 -> 111 (3 个计算置位,3 是质数)
9 -> 1001 (2 个计算置位,2 是质数)
10-> 1010 (2 个计算置位,2 是质数)

1、分析

       由于数据不是很多,所以我们可以遍历统计L~R中每一个数中1的个数是否是质数,若是则结果加1,然后由于每个数的大小不是很大,最多20位,所以我们可以先用一个哈希表把20以内的质数存起来,方便查询。

判断一个数n的第i位是否是1:n>>i&1

class Solution {
public:
    int countPrimeSetBits(int L, int R) {
        unordered_set<int> primes({2,3,5,7,11,13,17,19}); //先存储20以内的质数
        int res=0;
        for(int i=L;i<=R;i++) //遍历L~R中的每一个数
        {
            int s=0;
            for(int j=i;j;j>>=1) s+=j&1;  //统计每一个数1的个数
            if(primes.count(s)) res++;
        }
        return res;
    }
};

LeetCode 136 只出现一次的数

题目:https://leetcode-cn.com/problems/single-number/

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

说明:

你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

示例 1:

输入: [2,2,1]
输出: 1

1、分析

       这个题其实考察的就是异或运算,把所有的数异或起来,可以消除其中一对的数,还有就是任何数和0异或,结果都是本身。所以这个我们只需要把所有数异或起来,我们就可以得到那个单独的数。

2、 代码

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int res=0;
        for(auto x:nums) res^=x;
        return res;
    }
};

LeetCode 476 数字的补数

题目:https://leetcode-cn.com/problems/number-complement/

给定一个正整数,输出它的补数。补数是对该数的二进制表示取反。

注意:

给定的整数保证在32位带符号整数的范围内。
你可以假定二进制数不包含前导零位。
示例 1:

输入: 5
输出: 2
解释: 5的二进制表示为101(没有前导零位),其补数为010。所以你需要输出2。

1、分析

这里的求补数,不是我们我们之前意义上的求反码,例如5的二进制是101,那么补数就是010=2,对于前面的多个0不取反,只把已有的位数取反,所以我们需要知道当前数有几位。而且需要判断每一位是0还是1,并取反。

2、代码

class Solution {
public:
    int findComplement(int num) {
        int res=0,t=0;
        while(num) //遍历num的每一位
        {
            res+=!(num&1)<<t; //把num的当前最低位取反
            num>>=1;
            t++;
        }
        return res;
    }
};

 LeetCode 137 只出现一次的数字II

题目:https://leetcode-cn.com/problems/single-number-ii/submissions/

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。

说明:

你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

示例 1:

输入: [2,2,3,2]
输出: 3

1、分析

我们可以分析每一位1的个数,当当前位1的个数是3的倍数时,那么单独的数在当前位一定是1,若统计当前位1的个数不是3的倍数,那么单独的数在当前位一定是1,这样我们就把结果加进来。

2、代码

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int res=0;
        for(int i=0;i<32;i++) //查看每一位1的个数和
        {
            int s=0;
            for(auto x:nums)  //把每一个数在当前位计算1的个数和
            {
                s+=x>>i&1;
            }
            if(s%3) res+=1<<i; //若当前位1的个数不是3的倍数,则单独的数在当前位一定是1
        }
        return res;
    }
};

LeetCode 260 只出现一次的数字III

题目:https://leetcode-cn.com/problems/single-number-iii/submissions/

给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。

示例 :

输入: [1,2,1,3,2,5]
输出: [3,5]

1、分析

       我们同样可以先把所有数异或起来,那么最终的结果就是剩下的两个不同数的异或结果,对于这个结果肯定有一位是1,因为这两个数不一样,至少在某一位不一样,那么异或的结果就会是1,现在我们需要找出其中是1的那一位,然后根据此标识,可以把数据分成两类,一类是在这一位是1的一类,另一类是在这一位不是1的一类,在这两类中分别异或那么就可以分别得出那一个不成对的数。

2、代码

class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) {
        int s=0;
        for(auto x:nums) s^=x;  //两个不同数异或的结果
        int k=0;
        while(!(s>>k&1)) k++;   //找到s中某一位是1的位置
        int s2=0;
        for(auto x:nums)  
        {
            if(x>>k&1) s2^=x;  //根据第k位是否是1可以分成两类,把其中一类异或起来
        }
        //假设s=a^b,现在s2=a,那么b=s2^s=a^a^b=b
        return vector<int>({s2,s2^s});
    }
};

LeetCode 371 两整数之和

题目:https://leetcode-cn.com/problems/sum-of-two-integers/

不使用运算符 + 和 - ,计算两整数 ​​​​​​​a 、b ​​​​​​​之和。

示例 1:

输入: a = 1, b = 2
输出: 3

1、分析

       我们在这里用异或^和与&运算符来解决这个问题,异或即是不进位加法,而与则可以知道那一位需要进行进位,所以我们最终的结果就是异或的结果加上进位的结果,这里可以用递归解决。

2、代码

class Solution {
public:
    int getSum(int a, int b) {
        if(b==0) return a;
        int sum=a^b,carry=uint(a&b)<<1;  //由于是进位到下一位,所以需要左移一位,uint防止负数等数据
        return getSum(sum,carry);
    }
};

LeetCode 201 数字范围按位与

题目:https://leetcode-cn.com/problems/bitwise-and-of-numbers-range/

给定范围 [m, n],其中 0 <= m <= n <= 2147483647,返回此范围内所有数字的按位与(包含 m, n 两端点)。

示例 1: 

输入: [5,7]
输出: 4

1、分析

        对于m和n,我们那彼此的二进制来说,从高位到低位依次判断,两者是否一样,当找到第一个不一样的位时,前面所有位代表的结果即是最后的结果,比如m和n是:xxx0.....和xxx1.......,那么在从m到n不断增加的过程中,一定会经过一个xxx011111到xxx100000的过程,那么后面结果与一定会是0,所以我们可以从n的低位开始,依次去掉n的最低位1,当n不在大于m时,则返回当前n的值。

2、代码

class Solution {
public:
    int rangeBitwiseAnd(int m, int n) {
        while(m<n)
        {
            n&=(n-1);  //去掉低位的第一个1
        }
        return n;
    }
};

LeetCode 421 数组中两个数的最大异或值

题目:https://leetcode-cn.com/problems/maximum-xor-of-two-numbers-in-an-array/

给定一个非空数组,数组中元素为 a0, a1, a2, … , an-1,其中 0 ≤ ai < 231 。

找到 ai 和aj 最大的异或 (XOR) 运算结果,其中0 ≤ i,  j < n 。

你能在O(n)的时间解决这个问题吗?

示例:

输入: [3, 10, 5, 25, 2, 8]

输出: 28

解释: 最大的结果是 5 ^ 25 = 28.

1、分析

        我们先分析对于x,我们找一个和x取异或,使结果最大的y,那么应该如何寻找了。我们肯定是找一个和x的二进制尽可能不一样的数,我们在找的时候,由于每一位的权重是不一样的,最高位权重最大,所以肯定是从高位开始找和自身不一样的数。我们在这里可以用字典树来存储每一个数的二进制,在这里的字典树每一分支只有0或1。

QQ图片20180916224451.png

如图所示,假设x是25,我们现在找一个和x异或的结果最大的y,我们看x的最高位是1,从根节点出发,所以我们需要往0分支走,走到b,再看下一位还是1,所以继续往0分支走,走到c,继续看下一位是0,所以需要往1分支走,走到d,继续看下一位是0,我们需要往1分支走,但是当前分支没有1,所以只能往0走,走到e,继续看最后一位是1,所以需要往0分支走,但是当前分支没有0,所以只能走到1,即走到了5,所以使和25异或的结果最大的数是5。

2、代码

class Solution {
public:
    struct Node //构造一个字典树
    {
        int son[2];
    };
    vector<Node> nodes;
    int findMaximumXOR(vector<int>& nums) {
        nodes.push_back(Node({0,0})); //创造根节点
        for(auto x:nums)
        {
            int p=0;
            for(int i=30;i>=0;i--)
            {
                int t=x>>i&1;
                if(!nodes[p].son[t]) //p就是此节点的下标
                {
                    nodes.push_back(Node({0,0}));
                    nodes[p].son[t]=nodes.size()-1; //son里存的就是当前下标
                }
                p=nodes[p].son[t];
            }
        }
        int res=0;
        for(auto x:nums)
        {
            int p=0,max_xor=0;
            for(int i=30;i>=0;i--)  //从最高位开始看
            {
                int t=x>>i&1;
                if(nodes[p].son[!t])  //看是否有和当前位相反的分支
                {
                    p=nodes[p].son[!t];
                    max_xor+=1<<i; //加上当前位
                }
                else
                {
                    p=nodes[p].son[t];
                }
            }
            res=max(res,max_xor);
        }
        return res;
    }
};

 LeetCode 477 汉明距离总和

题目:https://leetcode-cn.com/problems/total-hamming-distance/

两个整数的 汉明距离 指的是这两个数字的二进制数对应位不同的数量。

计算一个数组中,任意两个数之间汉明距离的总和。

示例:

输入: 4, 14, 2

输出: 6

解释: 在二进制表示中,4表示为0100,14表示为1110,2表示为0010。(这样表示是为了体现后四位之间关系)
所以答案为:
HammingDistance(4, 14) + HammingDistance(4, 2) + HammingDistance(14, 2) = 2 + 2 + 2 = 6.

1、分析

        由于每一位都是独立的,我们可以独立开来看每一位,比如从个位开始,假设有4个数,个位分别是:0  1   0    1,那么他们之间的汉明距离总和是4,只有当其中一个是1,另一个是0时,汉明距离才会加一,所以我们可以统计出0和1的个数,然后相乘就是最后的结果。

2、代码

class Solution {
public:
    int totalHammingDistance(vector<int>& nums) {
        int res=0;
        for(int i=0;i<=30;i++)
        {
            int ones=0;
            for(auto x:nums)
            {
                if(x>>i&1) ones++;  //统计每一位1的个数
            }
            res+=ones*(nums.size()-ones); //1的个数乘以0的个数即是当前位的汉明距离和
        }
        return res;
    }
};

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!