位运算总结

位运算基本操作

位运算实现加法
// 位运算实现加法
int add(int a, int b) {
    while (b != 0) {
        int sum = a ^ b;         // 计算不考虑进位的和  
        int carry = (a & b) << 1; // 计算进位  
        a = sum;                // 更新a为新的和  
        b = carry;              // 更新b为新的进位  
    }
    return a; // 返回最终结果  
}
位运算实现除法
class Solution {
public:
    int divide(int dividend, int divisor) {
        if (dividend == INT_MIN && divisor == -1) return INT_MAX;
		long dvd = labs(dividend), dvs = labs(divisor), ans = 0;
		int sign = (dividend > 0) ^ (divisor > 0) ? -1 : 1;
		while (dvd >= dvs) {
			long temp = dvs, m = 1;
			while (temp << 1 <= dvd) {
				temp <<= 1;
				m <<= 1;
			}
			dvd -= temp;
			ans += m;
		}
		return sign == 1 ? ans : -ans;

    }
};

求绝对值
int abs_bitwise(int x) {
    int mask = x >> 31;  // 获取符号位,右移31位(假设是32位整数)
    return (x + mask) ^ mask;  // 如果是负数,mask为-1,否则为0
    //这里其实相当于对负数求了相反数,正数不变
    //~x+1 <=>~(x-1)
}

求相反数
int reverse_num(int x) {
    return ~x + 1;
}

int reverse_num2(int x) {
    return ~(x-1);
}

按位取反
int Reverse(int x) {
    return ~x;
}

交换二进制层面的奇偶位
int swap_odd_even(int x) {
     int a = 0b10101010101010101010101010101010;
     int b = 0b01010101010101010101010101010101;
         // 0xaaaaaaaa = 0b10101010101010101010101010101010 提取偶数位
         // 0x55555555 = 0b01010101010101010101010101010101 提取奇数位
	return ((x & a) >> 1) | ((x & b) << 1); // 偶数位右移一位(>>>),奇数位左移一位,
}

整数转二进制
string convert_int_to_bit(int x) {
    string res = "";
    for (int i = 31; i >= 0; i--) {
		res += (x >> i) & 1 ? '1' : '0';
	}
	return res;

}

浮点数转二进制
string convert_float_to_bit(float x) {
    string res = "0.";
    while(x!=0) {
        x *= 2;
        if (x>= 1) {
            res+='1';
            x -= 1;
        }
        else {
			res+='0';
		}
	}
	return res;
}

Q1:求只出现1次的数字,其余数字均出现k次

两种较为简单的方法:

  • 方法(1):开一个数组,统计二进制层面各个位上1的个数,最后对每位和k取模即可得到答案的二进制表示。
//统计二进制位
int signalNum(vector<int>& arr,int k) {
    int count[32] = {0};
    
    for (int num : arr) {
        for (int i = 0; i < 32; i++) {
			count[i] += (num >> i) & 1;
		}
    }

    int res = 0;
    for (int i = 0; i < 32; i++) {
        res|= (count[i] % k) << i;
    }

    return res;
}
  • 方法(2):
    我们已知2个相同的2进制数做不进位加法结果为0,3个相同的3进制数做不进位加法结果为0,所以不难推出k个相同的k进制数做不进位加法结果为0。

  • 由于只有一个数字出现一次,其余都出现k次,所以只需将所有数字进行k进制的不进位加法即可,和即为孤单数的k进制表示:

函数1:整数转k进制
string convert_int_to_kbit(int x, int k) {
	string res = "00000000000000000000000000000000";
    int cnt = 31;
    while (x) {
		res [cnt--] = (x % k)+'0';
		x /= k;
	}

	return res;
}
函数二:k进制转10进制:
int convert_kbit_to_int(string s, int k) {
	int res = 0;
    for (int i = 0; i < s.size(); i++) {
        res = res * k + (s[i] - '0');
	}
	return res;
}
函数三:k进制不进位加法:
string add_kbit(string a, string b, int k) {
    string res = a;

    for (int i = 0; i < a.size(); i++) {
        res[i] = (((a[i] - '0') + (b[i] - '0')) % k) + '0';
	}
    return res;
}
求n个k进制数的不进位和:

这个函数通过调用上面三个函数即可实现求n个k进制数的不进位和的功能。

//求n个k进制数的不进位和:
int sum_kbit(vector<int>& arr, int k) {
    int n = arr.size();

    //将每个数转换为k进制
    vector<string> s;
    for (int i = 0; i < n; i++) {
		s.push_back(convert_int_to_kbit(arr[i], k));
	}

    //k进制不进位求和
	string res = s[0];
    for (int i = 1; i < s.size(); i++) {
		res = add_kbit(res, s[i], k);
	}

    //将结果转换为10进制
    int ans = 0;
    ans= convert_kbit_to_int(res, k);

	return ans;
}

Q2:使用位运算解决天平称重问题

现拥有1,3,9,27等重量的砝码,每个只能用一次,求称出给定重量需要的砝码类型组合?(默认左边放物品,右边放砝码,如要称重量为5的物品,需要的组合是左边放物品、砝码1、砝码3,右边放砝码9,所以组合为9-3-1)

这里因为给的砝码都是3的幂,并且每个砝码只能用一次,所以可以考虑使用三进制来解决。

(1)可以将物品转为三进制,比如5转为三进制为012 。
(2)开一个数组用来记录右边放不放砝码(1为放,-1放左,0不放)
(3)然后从低位向高位遍历三进制,遇到0放0,遇到1放1,遇到2相邻高位加1放-1(相当于借一个砝码,使该为进1,然后还回去,所以该位砝码放一个到左边),遇到3相邻高位加1放0。 即012->[1,-1,-1](三进制层面,-1只代表放左还是放右)
(4)将记录数组转为砝码组合:从右往左扫描,-1就是放左边,1是放右边,0是跳过不放。所以5对应 放右-放左-放左–>即-1 * 1 - 1 * 3 + 1 * 9–>-1-3-9,所以组合为9-3-1

最后求幂的时候可以使用快速幂优化:

快速幂:求m^k%p
long long qsm(long long m, long long k, long long p)     //(m^k)%p
{
    long long res = 1, t = m;//res为结果,t为m^1
    while (k)
    {
        if (k & 1) res = res * t % p;//k为奇数时,res=res*t%p
        t = t * t % p; //底数平方,指数减半
        //cout << "t:" << t << "  res: " << res << endl;
        k >>= 1;
    }
    return res;
}

其余代码可以看文末


Q3:格雷编码

格雷编码(Gray Code)是一种二进制数字系统,在该系统中,相邻两个数之间只有一位二进制位不同。这种编码方式具有减少误差的优点,广泛应用于旋转编码器、数字电路和信号编码等领域。

vector<int> grayCode(int n) {
    vector<int> res;
    for (int i = 0; i < 1 << n; i++) {
        res.push_back(i ^ (i >> 1));
    }
    return res;

}

格雷编码也可以通过找规律求解:
二进制层面观察,其实每个位都是有固定的周期的,可以自行观察(代码在后面)。


Q4:求集合的子集

  • 思路
    类似于机器学习中的对于离散变量的编码,对于一个量来说,其二进制表达唯一,
    并且将其转换为二进制表达后,值为1的位表示取该元素,为0的位表示不取该元素。
    比如:{ 1,2,3,4 } 其子集{ 1、3、4 }对应的编码为1011,{ 2,4 }的编码为0101,这样每个子集都对应不同的编码。
    我们只需要遍历每个可能的编码即可,若对应位为1则表示取该元素,为0则表示不取该元素。

  • 注意
    不sort的话不好去重。去重如果使用set的话,如果不sort,可能会出现子集相同但是·子集里面对应元素的顺序不同·的情况,从而导致没有被去重

vector<vector<int>> subsetsWithDup(vector<int>& nums) {
    int n = nums.size();
    set<vector<int>> ret;
    sort(nums.begin(), nums.end());
    for (int S = 0; S < 1 << n; S++) {
        vector<int> tmp;
        for (int i = 0; i < n; i++) {
            if (S >> i & 1) {
                tmp.push_back(nums[i]);
            }
        }
        ret.insert(tmp);
    }
    vector<vector<int>> ans(ret.begin(), ret.end());
    return ans;
}

Q5:happpy number:

Digits 2, 3 and 6 are happy, while all others are unhappy. An integer is happy if it contains only happy digits in its decimal notation. For example, 2, 3, 263 are happy numbers, while 231 is not.

意思就是一个数只能有2,3,6 这三个数字组合而成,问第n个快乐数是多少?

#include <iostream>  
#include <vector>  
#include <string>  
#include<algorithm>

using namespace std;  

string convert_int_to_kbit(int x, int k) {
	string res = "00000000000000000000000000000000";
    int cnt = 31;
    int cha=0;
    while (x) {
        int mod=x % k;
        if(mod==0){
            res [cnt--]=cha+3+'0';
            cha=-1;
        }
		else{
            res [cnt--] = cha+mod+'0';
            cha=0;
            if(x!=1 && res[cnt+1]=='0'){
                cha=-1;
                res[cnt+1]='3';
            }
        } 
		x /= k;
	}

	return res;
}


int main() {  
    int n;  
    cin >> n;  

    string s=convert_int_to_kbit(n,3);
    //cout<<s<<endl;
    
    string ans="";
    for(int i=0;i<32;i++){
        if(s[i]=='1') ans+='2';
        else if(s[i]=='2') ans+='3';
        else if(s[i]=='3') ans+='6';
    }
    cout<<ans;
    return 0;  
}

Q6:所有数的按位与:

每次消去右边一个0:

class Solution {
public:
    int rangeBitwiseAnd(int m, int n) {
        while(n > m) {
            n &= n-1;
        }
        return n;

    }
};

相关代码:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <set>

using namespace std;

const long p = 1000000007;

/***************位运算基本操作********************/

// 位运算实现加法
int add(int a, int b) {
    while (b != 0) {
        int sum = a ^ b;         // 计算不考虑进位的和  
        int carry = (a & b) << 1; // 计算进位  
        a = sum;                // 更新a为新的和  
        b = carry;              // 更新b为新的进位  
    }
    return a; // 返回最终结果  
}

// 求绝对值
int abs_bitwise(int x) {
    int mask = x >> 31;  // 获取符号位,右移31位(假设是32位整数)
    return (x + mask) ^ mask;  // 如果是负数,mask为-1,否则为0
}

// 求相反数
int reverse_num(int x) {
	
    return ~x + 1;
}

// 求相反数2
int reverse_num2(int x) {
    return ~(x-1);
}

// 按位取反
int Reverse(int x) {
    return ~x;
}

// 交换二进制层面的奇偶位
int swap_odd_even(int x) {
     int a = 0b10101010101010101010101010101010;
     int b = 0b01010101010101010101010101010101;
         // 0xaaaaaaaa = 0b10101010101010101010101010101010 提取偶数位
         // 0x55555555 = 0b01010101010101010101010101010101 提取奇数位
	return ((x & a) >> 1) | ((x & b) << 1); // 偶数位右移一位(>>>),奇数位左移一位,
}

//整数转二进制
string convert_int_to_bit(int x) {
    string res = "";
    for (int i = 31; i >= 0; i--) {
		res += (x >> i) & 1 ? '1' : '0';
	}
	return res;

}

//浮点数转二进制
string convert_float_to_bit(float x) {
    string res = "0.";
    while(x!=0) {
        x *= 2;
        if (x>= 1) {
            res+='1';
            x -= 1;
        }
        else {
			res+='0';
		}
	}
	return res;


}


/***************Q1:求只出现1次的数字,其余数字均出现k次********************/

//方法一:

//n个k进制数做不进位加法:代码

//2个相同的2进制数做不进位加法结果为0:
//3个相同的3进制数做不进位加法结果为0:
//k个相同的k进制数做不进位加法结果为0:

//整数转k进制
string convert_int_to_kbit(int x, int k) {
	string res = "00000000000000000000000000000000";
    int cnt = 31;
    while (x) {
		res [cnt--] = (x % k)+'0';
		x /= k;
	}

	return res;
}

//k进制转10进制:
int convert_kbit_to_int(string s, int k) {
	int res = 0;
    for (int i = 0; i < s.size(); i++) {
        res = res * k + (s[i] - '0');
	}
	return res;
}

//k进制不进位加法:
string add_kbit(string a, string b, int k) {
    string res = a;

    for (int i = 0; i < a.size(); i++) {
        res[i] = (((a[i] - '0') + (b[i] - '0')) % k) + '0';
	}
    return res;
}

//求n个k进制数的不进位和:
int sum_kbit(vector<int>& arr, int k) {
    int n = arr.size();

    //将每个数转换为k进制
    vector<string> s;
    for (int i = 0; i < n; i++) {
		s.push_back(convert_int_to_kbit(arr[i], k));
	}

    //k进制不进位求和
	string res = s[0];
    for (int i = 1; i < s.size(); i++) {
		res = add_kbit(res, s[i], k);
	}

    //将结果转换为10进制
    int ans = 0;
    ans= convert_kbit_to_int(res, k);

	return ans;
}

//方法二:
// 
//统计二进制位
int signalNum(vector<int>& arr,int k) {
    int count[32] = {0};
    
    for (int num : arr) {
        for (int i = 0; i < 32; i++) {
			count[i] += (num >> i) & 1;
		}
    }

    int res = 0;
    for (int i = 0; i < 32; i++) {
        res|= (count[i] % k) << i;
    }

    return res;
}


/***************Q2:使用位运算解决天平称重问题********************/
//拥有1,3,9,27等重量的砝码,每个只能用一次,求称出给定重量需要的砝码类型组合

//快速幂:求m^k%p
long long qsm(long long m, long long k, long long p)     //(m^k)%p
{
    long long res = 1, t = m;//res为结果,t为m^1
    while (k)
    {
        if (k & 1) res = res * t % p;//k为奇数时,res=res*t%p
        t = t * t % p;
        //cout << "t:" << t << "  res: " << res << endl;
        k >>= 1;
    }
    return res;
}

//求给定重量的砝码组合
string MatchWeight(int w) {
    string s = convert_int_to_kbit(w,3);
    vector<int> cur(32);
    string ans= "";

    for (int i = 31; i >= 0; i--) {
        if (s[i] == '0');
        else if (s[i] == '1') cur[i] = 1;
        else if (s[i] == '2') {
            cur[i] = -1;
            s[i - 1]++;
        }
        else if (s[i] == '3') {
            cur[i] = 0;
            s[i - 1]++;
        }
    }
    for (int i = 0; i < 32; i++) {
		cout<<cur[i]<<" ";
	}
    cout<<endl;

    for (int i = 0; i < 32; i++) {

        if (cur[i] == 1) ans += ("+ "+to_string(qsm(3, 31 - i, p)) + " ");
        else if (cur[i] == -1) ans += ("- "+to_string(qsm(3, 31 - i, p)) + " ");
	}

    return ans;
}


/***************Q3:格雷编码********************/

//法一:
vector<int> grayCode(int n) {
    vector<int> res;
    for (int i = 0; i < 1 << n; i++) {
        res.push_back(i ^ (i >> 1));
    }
    return res;

}

//法二:找规律
vector<int> grayCode2(int n) {
    vector<int>ans(2 << (n - 1));
    ans[0] = 0;
    for (int i = 1; i < (2 << (n - 1)); i++) {
        int cnt = 1;
        int div = 2 << (n - 1);
        while (1) {
            if (i % (div) == 0) {
                cnt = div;
                break;
            }
            else {
                div >>= 1;
            }
        }
        ans[i] = (ans[i - 1] ^ cnt);
    }
    return ans;

}



/***************Q4:求集合的子集********************/

/*
思路
类似于机器学习中的对于离散变量的编码,对于一个量来说,其二进制表达唯一,
并且将其转换为二进制表达后,值为1的位表示取该元素,为0的位表示不取该元素。
。
比如:{ 1,2,3,4 }  其子集{ 1、3、4 }对应的编码为1011,{ 2,4 }的编码为0101,这样每个子集都对应不同的编码。
我们只需要遍历每个可能的编码即可,若对应位为1则表示取该元素,为0则表示不取该元素。

注意
不sort的话不好去重。去重如果使用set的话,如果不sort,可能会出现子集相同但是·子集里面对应元素的顺序不同·的情况,从而导致没有被去重
*/

vector<vector<int>> subsetsWithDup(vector<int>& nums) {
    int n = nums.size();
    set<vector<int>> ret;
    sort(nums.begin(), nums.end());
    for (int S = 0; S < 1 << n; S++) {
        vector<int> tmp;
        for (int i = 0; i < n; i++) {
            if (S >> i & 1) {
                tmp.push_back(nums[i]);
            }
        }
        ret.insert(tmp);
    }
    vector<vector<int>> ans(ret.begin(), ret.end());
    return ans;
}


int main() {

    //int x;
    //cin>>x;
    //cout<<convert_int_to_bit(x)<<endl;

    //float y;
    //cin>>y;
    //cout<<convert_float_to_bit(y)<<endl;

    //cout<<abs_bitwise(x)<<endl;
    //cout<<div_1(x)<<endl;
    //cout<<Reverse(x)<<endl;
    //cout<<Reverse(div_1(x))<<endl;
    //cout<<reverse_num(x)<<endl;
    //cout<<reverse_num2(x)<<endl;
    //cout<<swap_odd_even(x)<<endl;
    //int a, b;
    //cin>>a>>b;
    //cout<<add(a, b)<<endl;

    //vector<int>arr= {3, 5, 7,3,3,5,5};
    //cout<<sum_kbit(arr, 3)<<endl;
    //cout<<signalNum(arr, 3)<<endl;

    int w;
    cin>>w;
    cout<<MatchWeight(w)<<endl;
    return 0;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

追逐远方的梦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值