马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
绪论:打击蓝桥杯一起加油!!
逐日鼓励:“不设限和自我肯定的心态:I can do all things。 — Stephen Curry”
绪论:
本日总结了下位利用中常见的使用的方法,而且附加许多练习,通过方法 + 练习,位利用算法就将有很大的提升,后续将连续更新前缀和算法。敬请等待~
————————
早关注不迷路,话不多说安全带系好,发车啦(发起电脑观看)。
位运算
起首相识根本的位运算利用(必备):
- <<:左移,全部位数左移(如:1111 << 1 = 1110)
- >>:右移,全部位数右移(如:1111 >> 1 = 0111)
- ~:取反,将全部位数全部取反(0 -> 1/ 1 -> 0)、如: ~1111 = 0000
- 至于 & 、| 、^ 看下图左边运算(001 与 011举行位运算利用,得到的效果)
- &:按位与,有0就是0
- |:按位或,有1就是1
- ^:按位异或,雷同为0,相异位1(大概可以当作无进位的加法)
- 约定下标从右往左的0开始:
- 如许某位置移动到0位置的移动值就是自己的下标
- 这里的下标,并不是数组中的真下标,而是我们在影象位数时它的位置的标识,从0开始到31(而且留意是从右往左的影象)
- 对于位运算的优先级,就不影象了,就经量使用括号
常用的根本位运算利用
- 判断一个数的二进制中第x位为0/1
- 方法很简单 联合上面的>> 、 &位运算符
- 此中留意在位运算中 会经常用到 1,来辅助位运算(由于他的二进制中只有1位:000… 0001,如许就能通过这一位举行利用,也能克制许多复杂题目)
- 此处的话我们可以将 某个数举行右移>>x位,再 按位与x>> 上1(由于对与&来说,有0就是0,如许的话,按位与上1的话,若第x位上是0则为0,反之为1的话和按位与的1都没有0,就为1,而对于其他位来说差异管由于1的除了第一位为1其他都是0,按位与后都为0,以是效果只用两种大概 000…0000 / 000…0001)
- 详细如下图假设
- n为:…0110101001、
- 1: …000…0001
- 通过将n右移x位,将x位的值移动第一位,在与000…01举行按位与,即可得到答案
对于背面的就不再细讲了,发起写草稿模仿环境就能很好的明白了,详细有题目可以品评。
- 将数 n 的二进制修改成1:n |= (1 << x)
练习标题:leetcode 191 338 461
- 将一个数的二进制体现的第x位修改成 0:n &= (~(1<<x))
- 位图头脑:位图中我们就是通过 位利用举行管理此中的数据,位图内部告急通过0/1来判断该数据是否存在,我们可以用于存储一已知的容器数据但它访问速率大概较慢,那么可以将他在存放到位图中,如许通过 常量级的位利用就能快速的知道数据的状态。
- 提取一个二进制数中的最右侧的1:n &= -n
- 干掉二进制数中最右侧的1:n &= (n-1)
- 一些按位异或的运算律
- a ^ 0 = a
- a ^ a = a
- a ^ b ^ c = a ^ (b ^ c)
- 对于a ^ b ^ c来说他的本质是:可以明白为按位异或利用是有互换律的,我们能同时的看三个数,它们的序次无所谓,那么也就能当作:抵消1的情势快速的算出答案(详细如下图)
相干练习:LeetCode136、260
怎样将 a ^ b 分别出来
此中就260比力特别和困难:这里讲一下
- 想通过 按位与利用得到两个单独的数据 a ^ b
- 如今须要将他给区分开
- 对于 a ^ b来说,若要区分可以通过找1,由于按位异或的本质是:雷同为0,相异为1
- 以是可以找到第一个差异的地方,也就是最右边的1
- 通过这个1的举行区分两个数,并将这两个数给分别通过按位异或的方式放到一个数组中
- 如许终极就能将这两个数放到两个数组中,也就能单独得到了
- class Solution {
- public:
- vector<int> singleNumber(vector<int>& nums) {
- int res = 0;
- for(auto c :nums){
- res ^= c;//利用到 res ^ res = 0 & res & 0 = res 这两个按位异或技巧
- //当res 在按位异或的过程中会将出现两次的给异或为0,而res 与 0 异或则还是res
- }
- //此时因为会有两个单独的值,所以res = a ^ b
- // 1001
- // 0011
- // 1010:a ^ b
- //此时找到 a ^ b 的最右边的1(这个代表他们不同的位)
- //通过这个区分 a b 这两个数,然后再次遍历nums中的元素
- //不过此时加上条件:通过 右移最右边这个数的下标i,后判断是否为1进行区分两数,将两数有分成了两种情况
- //这样 对于 右移最右边这个数的下标i 为1 / 不为1 中,肯定最终只会留下 a 和 b(因为其他的还是重复的然后抵消了)
- int i = 0;
- for(; i < 32;i++){
- if(((res >> i) & 1) == 1){
- break;//代表找到最右边的1了
- }
- }
- int res1 = 0,res2 =0;
- //得到i的位置
- for(auto c : nums){
- if((c >> i) & 1 == 1){
- //一份是:右移i位等于1的,同时也是 a ^ b中的某一位
- res1 ^= c;
- }
- else{
- res2 ^= c;
- }
- }
- vector<int> v{res1,res2};
- return v;
- }
- };
复制代码 ————
将上面8个常见的环境记取并练习,后在举行下面的练习进一步巩固
详细练习:
1. 判断字符是否唯一
标题:
分析标题并提出,办理方法:
- 本题不难想到,通过一个hash来快速简直定元素出现的个数
- 但标题给了一个小小的提升:能不能倒霉用额外的空间
- 那么本题仅仅26位,我们是不是可以通过位图的头脑:用一个整形来直接取代hash
- 联合前面的位利用,实现判断某个字符是否已经存在过
- 加上鸽巢原理:当字符串长度凌驾 26 时肯定会有重复的字符!!
- 修改x位为1:n|= (1 << i)
题解焦点逻辑:
- class Solution {
- public:
- bool isUnique(string astr) {
- if(astr.size() > 26 ) return false;//利用鸽巢原理进行优化
- //使用位图 代替hash
- int bit = 0;
- for(auto c : astr){
- int i = c - 'a';//得到字符的位置: 'a' - 'a' = 0、'b' - 'a' = 1
- //判断字符是否已经出现过:
- if((bit >> i & 1) == 1){
- //进来代表第 i 位为1,并且代表已经出现过一次了
- //所以就错误了,直接返回
- return false;
- }
- bit |= (1 << i);//将第i位设置为 1
-
- }
- return true;
- }
- };
复制代码
2. 丢失的数字
标题:
分析标题并提出,办理方法:
方法1:hash(空间复杂度:O(N))
高斯求和法:((首项 + 尾项) * 项数)/ 2 便是全部元素的和,再减去数组中的和,如许就能得到缺失的和(空间复杂度:O(1))
方法3(本题解法):位运算 a ^ a = 0(空间复杂度:O(1))
题解焦点逻辑:
本题就告急写位运算的利用:
- class Solution {
- public:
- int missingNumber(vector<int>& nums) {
- int n = nums.size();
- int res = 0;
- for(int i = 0; i <= n;i++){
- res ^= i;//得出 从 0 ~ n 的所以情况 0 ^ 1 ^ .... ^ n
- }
- for(auto i : nums){
- res ^= i;// res ^ nums = 缺失的
- }
- return res;
- }
- };
复制代码
3. 两整数之和
标题:
分析标题并提出,办理方法:
对于这种不能使用 + - 法的一样寻常就是位运算了
按位异或的无进位相加
- 那么就须要找到他的进位值:发现当 a & b 时就能体现他是否须要进位(下图右边)
a & b 检察否须要进位
- 但这只是找到了他是否要进位的地方,我们还须要将他左移 1 位 从而完成进位后的低位++
- 然后再一次举行加法利用(按位异或:无进位加法)将进位值加上得到终极值
- 但留意:此时还须要再一次判断是否尚有进位的地方:
- 若为0则体现不存在进位了
- 若不为0则代表仍旧有进位,那么就须要再次重复2、3、4、5步(下图黑框框)
题解焦点逻辑:
- class Solution {
- public:
- int getSum(int a, int b) {
- //使用 ^ 的无进位相加
- //再使用 & 判断是否要进位(为0则代表不进位、不为0则代表有进位)
- while((a & b) << 1){
- int tmp = a;
- a = a ^ b;
- b = (tmp & b) << 1;
- }
- return a ^ b;
- }
- };
复制代码 4. 只出现一次的数字 II
标题:
分析标题并提出,办理方法:
题解焦点逻辑:
- class Solution {
- public:
- int singleNumber(vector<int>& nums) {
- //将 nums 中的每个数的每一位 都相加并最终 模3,最终得到的每一位就是 只出现一次的每一位:为0 就是0,为1就是1
- //此处 还能 %n 这样就能 看出就能排除重复出现n次 得到只出现1次的
- int res = 0;
- // n * 32
- for(int i = 0 ; i < 32;i++){
- //使用一个sum计算,判断在该位置的和
- int sum = 0;
- //3n0 + 0 = 0 -%3-> 0
- //3n0 + 1 = 1 -%3-> 1
- //3n1 + 0 = 3n -%3-> 0
- //3n1 + 1 = 3n + 1 -%3-> 1
- for(auto c : nums){
- //判断c位置上 i 下标下的值
- if(((c >> i) & 1) == 1){//判断是否为1,为1则++
- sum++;
- }
- }
- sum %= 3;//判断只出现一次的那个数的下标
- if(sum == 1){
- res |= (1 << i);//给结果下标添加算出来的值
- }
- }
- return res;
- }
- };
复制代码
5. 消散的两个数字
标题:
分析标题并提出,办理方法:
分析标题不难通过前面的铺垫想出:
- 通过创建下同巨细的数组与缺失数组举行 按位异或 利用,终极会得到缺失两个数的 a ^ b
- 此时通过 a ^ b 找到它末了一位1的位置
- 将原数组和缺失数组都分成两份,而且这两份中会各自有a和b(获取没a和b)
- 如许再将分开的数组 相互 按位异或 就得到了分别缺失的两个a和吧
题解焦点逻辑:
- class Solution {
- public:
- vector<int> missingTwo(vector<int>& nums) {
- int n = nums.size() + 2;
- // 创建原数组,这里本质不用创建,直接不断 ^ 存储即可!
- int sum = 0;
- for(int i = 1 ; i <= n ;i++){
- sum ^= i;
- }
- //先与缺数数组相 按位异或
- int absent_value = sum;
- for(auto c : nums) {
- absent_value ^= c;
- }
- //得到了 a ^ b 缺失的值,现在找到第一个不同的位置
- int i = 0;
- for(; i < 32 ;i++){
- if((absent_value >> i) & 1 == 1){
- break;
- }
- }
- //找到了i的位置,将原数组和缺失数组再次分成两份,再分别相异或
- vector<int> arr(2,0);
- for(int j = 1;j <= n;j++){
- if(((j >> i) & 1) == 1){
- arr[0] ^= j;
- }else{
- arr[1] ^= j;
- }
- }
- for(auto c : nums) {
- if(((c >> i) & 1) == 1){
- arr[0] ^= c;
- }else{
- arr[1] ^= c;
- }
- }
- return arr;
-
- return arr;
- }
- };
复制代码
|