数据结构-位运算

一、异或

1、hot100 - 136. 只出现一次的数字(一个元素出现一次,其余出现两次)

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

  • 题解:全员异或
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int res = nums[0];
        for(int i=1; i<nums.size(); i++){
            res ^= nums[i];
        }
        return res;
    }
};

2、剑指 Offer 56 - I. 数组中数字出现的次数(两个元素出现一次,其余出现两次)

题目:https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/

  • 解法:
    • 如果只有一个数字只出现一次,其他数字都出现两次,进行全员异或即可;
    • 若有两个数字出现一次,其他数字都出现两次,需要分组,分组条件:
      • 两个独特的数字分成不同的组;
      • 相同的数字分成相同的组。
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
    int ret = 0;
    	//ret为两个只出现一次的数字的异或结果
        for(int i: nums) ret ^= i; 
    int div = 1;
    	//找到ret中第一个二进制为1的位置div
        while((div&ret)==0) div <<= 1;
    int a = 0, b = 0;
        for(int i: nums){
        	//div与第一个只出现一次的数字&为1时;
        	//div与另一个只出现一次的数字&必为0;
        	//相同的数字在div位的二进制值必相同;
        	//可以将相同数分在同一组内。
            if((div & i)) a ^= i;
            else b ^= i;
        } 
        return vector<int>() = {a, b};
    }
};

二、一般位运算

1、剑指 Offer 56 - II. 数组中数字出现的次数 II(一个元素出现一次,其余出现三次)

题目:https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-ii-lcof/

  • 解法:
    • 如果一个数字出现三次,那个它的二进制表示的每一位(0或者1)也出现三次;
    • 如果把所有出现三次的数字的二进制表示的每一位都分别加起来,那么每一位的和都能被3整除;
    • 把数组中所有数字的二进制表示的每一位都加起来;
      • 如果某一位的和能被3整除,那么只出现一次的数字二进制表示中对应的那一位是0;
      • 否则是1。
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int data[32] = {0};
        for(int num: nums){
            long int div = 1; //防止溢出使用long int
            for(int i=0; i<32; i++){
                if(div & num) data[i]++; 
                div <<= 1;
            }
        }
        int res = 0;
        for(int i=31; i>=0; i--){
            res <<= 1;
            if(data[i]%3) res |= 1;
        }
        return res;
    }
};

2、Leetcode - 191. 位1的个数【蔚来】

题目:https://leetcode.cn/problems/number-of-1-bits/solution/

方法一:循环检查二进制位

  • 时间复杂度:O(k),其中k是int 型的二进制位数,k=32。我们需要检查n的二进制位的每一位,一共需要检查32 位。
  • 空间复杂度:O(1)。
class Solution {
public:
    int hammingWeight(uint32_t n) {
        int ret = 0;
        uint32_t flag = 1;
        for (int i = 0; i < 32; i++) {
            if (n & flag) {
                ret++;
            }
            flag <<= 1;
        }
        return ret;
    }
};

方法二:位运算优化

  • 观察这个运算:n&(n−1),其运算结果恰为把n的二进制位中的最低位的1变为0之后的结果。
  • 如:6 & (6−1)=4,6=(110)2 ,4=(100)2 ,运算结果4即为把6的二进制位中的最低位的1变为0之后的结果。
  • 这样可以利用这个位运算的性质加速我们的检查过程,在实际代码中,不断让当前的n与n−1做与运算,直到n变为0即可。因为每次运算会使得n的最低位的1被翻转,因此运算次数就等于n的二进制位中1的个数。
  • 时间复杂度:O(logn)。循环次数等于n的二进制位中1的个数,最坏情况下n的二进制位全部为1。我们需要循环logn次。
  • 空间复杂度:O(1),我们只需要常数的空间保存若干变量。
class Solution {
public:
    int hammingWeight(uint32_t n) {
        int ret = 0;
        while (n) {
            n &= n - 1;
            ret++;
        }
        return ret;
    }
};

三、Brian Kernighan 算法

概述:

  • 该算法可以被描述为这样一个结论:记f(x)表示x和x-1进行与运算所得的结果(即f(x)=x&(x-1)),那么f(x)恰为x删去其二进制中最右侧的1的结果。
    在这里插入图片描述

1、hot100 - 338. 比特位计数

题目:https://leetcode-cn.com/problems/counting-bits/

方法一、Brian Kernighan 算法

  • 解法:
    • 令x = x&(x-1),该运算将x的二进制表示的最后一个1变成0;
    • 对x重复该操作,直到x变成0,则操作次数即为x的一比特数
  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(1)
class Solution {
public:
    int count(int x){
        int ans = 0;
        while(x){
            x &= (x-1);
            ans++;
        }
        return ans;
    }
    vector<int> countBits(int n) {
        vector<int> res = {0};
        for(int i=1; i<=n; i++){
            res.push_back(count(i));
        }
        return res;
    }
};

方法二、动态规划-最高有效位

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

2、hot100 - 461. 汉明距离

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

方法一、内置位计数功能函数

  • 时间复杂度:不同语言的实现方法不一,可以近似认为其时间复杂度为O(1)
  • 空间复杂度:O(1)
class Solution {
public:
    int hammingDistance(int x, int y) {
    	//__builtin_popcount():c/c++中计算一个无符号整数有多少个位1
        return __builtin_popcount(x^y); 
    }
};

方法二、异或运算

  • 解法:

    • 记s=x^y(x异或y);
    • 不断的检查s的最低位;
    • 如果最低位为1,那么计数器+1;
    • 之后令s整体右移一位(这样s的最低位将被舍去,原本的次低位就变成了新的最低位);
    • 一直重复以上过程,直到s=0为止。
      在这里插入图片描述
  • 时间复杂度:O(logC)。其中C是元素的数据范围,在本题中 logC=log2^31=31

  • 空间复杂度:O(1)

class Solution {
public:
    int hammingDistance(int x, int y) {
        int s = x^y, res = 0;
        while(s){
            res += s&1;
            s >>= 1;
        }
        return res;
    }
};

方法三、Brian Kernighan 算法

  • 时间复杂度:O(logC),其中C是元素的数据范围,在本题中 logC=log2^31=31。
  • 空间复杂度:O(1)
class Solution {
public:
    int hammingDistance(int x, int y) {
        int s = x^y, res = 0;
        while(s){
            s &= (s-1);
            res++;
        }
        return res;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值