位运算与二进制

前言

主要记录一些使用二进制的操作,便于高效解题。

基本运算与规律

我们先来说说这个位运算的基本运算规则以及可以用于解题的基本规律。

& 运算

1&1=1

1&0=0

0&0=0

^ 运算

1^1=0

1^0=1

0^0=0

| 运算

1|1=1

1|0=0

0|0=0

左右移运算

a<<1:a左移一位

a>>1:a右移一位

基本规律

1. 计算机在做运算的时候,本质上是在进行二进制运算,这就意味着我们可以充分利用这个性质来达到意想不到的结果

2.K进制的K个数字的不进位加法求和为0

3.一个二进制位与1做异或运算,可以保存当前的状态

4.二进制数做减法会将最低位的1转化为0

这个的话结合我们的规律1会发生很多非常有意思的操作。
那么接下来我会按照顺序介绍一些用法。

案例

^运算妙招

给一个需求,例如一个数组[1,1,2,2,3,3,4,5,5]

除了某一个数字,其他的数字都是成对出现的,现在让你找到这个没有成对出现的数字,例如例子里面的4.

方案1 hash法

首先我们是很容易想到hash法的,但是这样一来的话,空间复杂度显然是不好的,并且需要遍历一遍hash数组,才能够知道答案。

public class 位运算1 {
    
    

    public static void main(String[] args) {
    
    

        int[] a = new int[]{
    
    1,1,2,2,3,3,4,5,5};

        int[] hash = new int[a.length];

        for (int j : a) {
    
    
            hash[j]++;
        }

        for (int i = 0; i < hash.length; i++) {
    
    
            if(hash[i]==1){
    
    
                System.out.println(i);
            }
        }

    }

}

方案2 ^ 运算

方案一时间空间都不好,那么想要解决这个方案其实就需要使用到^运算了。

由于 :1^1=0 0^0=0

所以当 3和3做异或的时候结果必然是0,这样一来成对的异或都是0,那么0和4做异或自然就是4

public class 位运算1 {
    
    

    public static void main(String[] args) {
    
    
        //找出不同
        int[] a = new int[]{
    
    1,1,2,2,3,3,4,5,5};

          int x = 0;
          for (int i : a) {
    
    
              x = x^i;
          }
          System.out.println(x);
    }

}

统计位为1的个数

给定一个十进制数,叫你看看这个十进制数字里面的位里面有几个1

例如 3–>011
这里就有两个

由于这里有多种解法,所以我这里就一起来说说了。

最简单的处理方式

同样的,我们很容易想到直接把这个十进制转化为2进制然后去统计1.

public class 运算二 {
    
    
    public static void main(String[] args) {
    
    
        int a = 3;
        char[] chars = Integer.toString(a, 2).toCharArray();
        int count = 0;
        for (char aChar : chars) {
    
    
            if(aChar=='1'){
    
    
                count++;
            }
        }
        System.out.println(count);
    }
}

这个是最容易想到的方案之一。那么这里的话其实也是可以使用位运算来快速的做的。

运用移运算

image.png

思路就是这样系,写起来就好办了。

public class 运算二 {
    
    
    public static void main(String[] args) {
    
    
        int a = 3;
        int count = 0;
        for (int i = 0;i<32;i++){
    
    
            //4个字节32位
            if((a&(1<<i)) == (1<<i)){
    
    
                count++;
            }
        }
        System.out.println(count);
    }
}

当然这里使用的是左移,我们把a进行右移也是ok的。

减1妙用

还记得先前写的第3点吧。
如果一个数字,例如 a=3

image.png

public class 运算二 {
    
    
    public static void main(String[] args) {
    
    
        int a = 3;
        int count = 0;
        while (a!=0){
    
    
            a = a&(a-1);
            count++;
        }
        System.out.println(count);
    }
}

| 运算之状态转换

这个还记得到,我们先前的题目嘛

状态压缩+动态规划(蓝桥杯2019 java A组 糖果问题)

其中有一个非常巧妙的东西,那就是把下面的这个单原组转化为一个状态数,便于dp

6 5 3  
1 1 2  
1 2 3  
1 1 3  
2 3 5  
5 4 2  
5 1 2  
6 5 3    状态压缩
		二进制  十进制
1 1 2   00011	3
1 2 3	00111	7
1 1 3	00101	5

我们的代码是这样操作的

w[i] |=(1<<k-1) ;

这个w是保存我们的状态的
当时dp的含义是 当前状态要多少包糖果

这里插一嘴关于dp数组的构建思想,你只要记住这个dp的value永远要保存的是,当前的最优解,例如这个糖果问题,要求最少包数,首先看到最小,最大字眼,必然是dp或者贪心,但是贪心懂的都懂,直接考虑dp(无非是0-1背包问题或者多重背包等)要求这个包数,那么必然dp[i] = value 这个value就必须是包数,那么这个i是什么,那么必然是决定,或者影响value的变量。那么在这里如果我们不懂得如何保存状态的话,那么我们最后构建的dp很有很有可能是这样的dp[][][]…=value 这个[]的个数取决你有多少种糖果。

那么这个是如何转化的呢,我先前没说清楚,这里重新说一下。
其实很简单,我们最后要搞的是
0 0 0 0 0这种格式的二进制形式(当然最后对于计算机会转换为10进制,这样我们就直接使用了一个数字表示一个状态)

我们先看到
1 1 2
这一组数据

0 0 0 0 0 初始状态
之后我们把以拿过来,左移 1-1位然后做或运算
于是得到了
0 0 0 0 1

再来一个1
还是
0 0 0 0 1
之后是2 左移2-1个单位然后位运算

0 0 0 1 1

于是 3 就表示了 1 1 2 这个状态。

所以位运算还是很巧妙的。
蓝桥杯要玩的话我觉得还是很有可能要玩 这个状态压缩的类型的,状态压缩然后套上背包问题。

关于背包问题的话,我这边也在总结过程当中,因为很多问题,首先是搜索问题,基本上我们直接DFS ,BFS 想都别想别的,顶多想想二分。如果是dp问题,有什么最值问题啥的,基本上dp,然后这些dp有基本上是背包问题的变种,我目前见到最多的无非就是完全背包,0-1背包,然后我们再套个模型,然后做空间优化,基本上就这样,其他的怎么说呢,看实际情况,当然还有一些数学问题,毕竟蓝桥杯是个算法竞赛嘛,还是会来一些数学问题的,要掌握一些数学理论,不过基本上我见到了我都会放弃,除非可以暴力穷举(也就是回溯嘛)(所以掌握三大点,递归回溯,搜索(DFS,BFS,二分),dp(八大背包问题))然后往死刷,当然这个只是我目前的想法,如有大佬多多指教(评论区)
说实话,不是专业的竞赛选手

猜你喜欢

转载自blog.csdn.net/FUTEROX/article/details/123482918