zoukankan      html  css  js  c++  java
  • 位运算 位运算的常见用法/题目

    定理1:两个相同的数字做异或(^), 等于0

    • 0与任何数字异或还是该数字本身 
    • 例题:136. 只出现一次的数字  -- 除了一个数字出现一次,其他都出现了两次,让我们找到出现一次的数
    • 解法:直接把所有的元素一起异或(^),剩下的就是只出现一次的数字。

      

    定理2:把0的第i位变成1:0 ^ (1<<i)

    • 0与任何数字异或还是该数字本身 

    定理3:把该整数最右边一个1变成0:把该整数减去1,再和原来的整数做与(&)运算

    把一个整数减去1,再和原来的整数做与运算,会把该整数最右边一个1变成0。

    那么一个整数的二进制表示中有多少个1,就可以进行多少次这样的操作。

    eg:

    原整数为 1110...

    1110 & (1110 - 1) = 1110 & 1101 = 1100

    1100 & (1100 - 1) = 1100 & 1011 = 1000

    1000 & (1000 - 1) = 1000 & 0111 = 0000

    > 判断一个是是不是2的整数次方

    如果是,那么2进制中有且只有一位是1 !!! 

    > 计算要改变多少位,M才能变成N

    先计算M和N的异或,得出有多少位不同,也就是多少位1

    然后再计算1的个数

    > 判断奇偶数 / 判断最右一位是否是1

    让该数字直接与1做与运算。如果结果是1,那么该数字最右一位一定是1.

    eg:

    1101 & 0001 = 0001

    1100 & 0001 = 0000

    > 依次判断每一位是否为1

    n为要输入的数字,flag从1开始,依次左移一位去挨个判断n的每一位

    1 while(flag){
    2     if(n & flag){
    3         //do someting...
    4     }
    5     flag = flag << 1;
    6 }

    > 快速乘以2,除以2

    左移运算符,<<,等同于乘以2

    右移运算符,>>,等同于除以2

    位运算比乘除法效率高得多!!!

    通解通法:除了一个数字出现一次,其他都出现了N次

    例题:137. 只出现一次的数字 II -- 除了一个数字出现一次,其他都出现了三次,让我们找到出现一次的数

    题解:https://leetcode-cn.com/problems/single-number-ii/solution/zhi-chu-xian-yi-ci-de-shu-zi-wei-yun-suan-ke-yi-tu/

    统计所有数字中每个位中1出现的总数,那么对于某个位,1出现的次数一定是3的倍数或者是1或者0,那么对这个数%3得到的结果就是目的数字在该位上的值。

    也就是说:

    • 如果所有数字都出现3次, 那么将所有的这些数字的每一位的0或者1累加起来后,也都能被3整除。
    • 如果只有一个数字出现一次,其他所有数字都出现3次, 那么将所有的这些数字的每一位的0或者1累加起来后,不能被3整除的那一位肯定是只出现一次的数字造成的。
    class Solution {
    public:
        int singleNumber(vector<int>& nums) {
            int ans = 0;
            for (int i = 0; i < 32; i++) {  // 确定每一位:int整数是32bit
                int sum = 0;
                for (int num : nums) {
                    sum += (num >> i) & 1;  // 统计nums数组中每一个num,的第i位是不是1,然后加在sum上。>>i 右移操作就是为了和1做与运算,才能计算第i位是不是1
                }
                ans ^= (sum % 3) << i;   // 对3取余,然后把结果左移i,和ans取异或,就能将结果放到应该在的第i位
            }
            return ans;
        }
    };
    

      

    通解通法:除了两个数字出现一次,其他出现两次 -- 巧用mask进行分类

    例题:

    解析:只出现一次的数字 III

    https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/solution/jie-di-qi-jiang-jie-fen-zu-wei-yun-suan-by-eddievi/

    由于数组中存在着两个数字不重复的情况,我们将所有的数字异或操作起来,最终得到的结果是这两个数字的异或结果:(相同的两个数字相互异或,值为0)) 最后结果一定不为0,因为有两个数字不重复。

    演示:

    4 ^ 1 ^ 4 ^ 6 => 1 ^ 6

    6 对应的二进制: 110
    1 对应的二进制: 001
    1 ^ 6 二进制: 111
    此时我们无法通过 111(二进制),去获得 110 和 001。
    那么当我们可以把数组分为两组进行异或,那么就可以知道是哪两个数字不同了。
    我们可以想一下如何分组:

    重复的数字进行分组,很简单,只需要有一个统一的规则,就可以把相同的数字分到同一组了。例如:奇偶分组。因为重复的数字,数值都是一样的,所以一定会分到同一组!
    此时的难点在于,对两个不同数字的分组。
    此时我们要找到一个操作,让两个数字进行这个操作后,分为两组。
    我们最容易想到的就是 & 1 操作, 当我们对奇偶分组时,容易地想到 & 1,即用于判断最后一位二进制是否为 1。来辨别奇偶。
    你看,通过 & 运算来判断一位数字不同即可分为两组,那么我们随便两个不同的数字至少也有一位不同吧!
    我们只需要找出那位不同的数字mask,即可完成分组( & mask )操作。

    由于两个数异或的结果就是两个数数位不同结果的直观表现,所以我们可以通过异或后的结果去找 mask!
    所有的可行 mask 个数,都与异或后1的位数有关。

     

    为了操作方便,我们只去找最低位的mask(只保留最低位的1):

    代码

    class Solution {
        public int[] singleNumbers(int[] nums) {
    		if(nums.length == 2){
    			return nums;
    		}
    		int subResult = 0;
    		for(int i = 0; i < nums.length; i++){
    			subResult = subResult ^ nums[i];
    		}
    		int mask = 1;
    		while( (mask & subResult) == 0){
    			mask = mask << 1;
    		}
    		int a = 0;
    		int b = 0;
    		
    		for(int i = 0; i < nums.length; i++){
    			if( (nums[i] & mask) == 0){
    				a = a ^ nums[i];
    			}else{
    				b = b ^ nums[i];
    			}
    		}
    		return new int[]{a,b};
    		
        }
    }
    

    不用加、减、乘、除...

    不用加、减、乘、除,做加法

    第一步:先做“异或”操作,相当于先忽略了进位,只计算了每个位的计算

    17 (10001) + 5 (00101)

          10001

          00101

    ->  10100

    第二步:再做“与”操作,只计算进位

          10001

          00101

    ->  00001

    第三步:对第二部的进位,左移一位(因为是进位啊,所以肯定要加到左移一位上面)。再重复第一步+第二步,直到进位为0

    第一步结果      10100

    第二步进位      00010 (00001左移了一位)

    ->                 10110

    不用乘、除、mod,做除法

    假设计算16/3

    方法1:用16每减去3一次,当每次的差依然大于0,结果(商)就加1。

    缺点:会超时...除数增加的太慢

    方法2:倍数倍增法。

    除法的商就是可以从被除数中减去除数的次数而不使其为负的次数。下以成倍次数增加除数并从被除数中减去已经增加的除数次数。

    1.比如15 / 3,首先从等于当前除数开始,如果除数左移一位(即增长一倍为6)小于被除数,则除数左移一位同时用变量p记录除数增长倍数,
    接着除数在左移一位(即增长一倍为12)小于被除数,则除数左移一位同时p记录除数增长倍数(p左移一位,即表示增长4倍),
    除数再左移一位(即增长为24)大于15,则停止内循环,

    2.然后从被除数中减去当前除数,同时将增长倍数4加到结果集中,

    3.再进行外循环(15 - 12 = 3 >= 3),

    4.重复上述过程,当前可以继续左移一位,所以最终结果为4 + 1 = 5。

    下面的图表,假设计算16/3,除数每次左移1位,倍增:

    ---------------------------------

    被除数   除数  临时结果           最终结果(商)

    ------------第一次循环-----------

    16          3         1                        

    16          6         2

    16          12        4

    16          24       NA(16<24)      4

    ------------第二次循环-----------

    16-12=4  3                                 4

    4           3          1

    4           6          NA(4<6)        4+1=5

    4-3=1    3         NA(1<3)

  • 相关阅读:
    使用MobaXterm远程连接Ubuntu,启动Octave,界面不能正常显示
    ABP .Net Core 日志组件集成使用NLog
    ABP .Net Core Entity Framework迁移使用MySql数据库
    ABP前端使用阿里云angular2 UI框架NG-ZORRO分享
    阿里云 Angular 2 UI框架 NG-ZORRO介绍
    Visual Studio 2019 Window Form 本地打包发布猫腻
    VS Code + NWJS(Node-Webkit)0.14.7 + SQLite3 + Angular6 构建跨平台桌面应用
    ABP .Net Core 调用异步方法抛异常A second operation started on this context before a previous asynchronous operation completed
    ABP .Net Core To Json序列化配置
    .Net EF Core数据库使用SQL server 2008 R2分页报错How to avoid the “Incorrect syntax near 'OFFSET'. Invalid usage of the option NEXT in the FETCH statement.”
  • 原文地址:https://www.cnblogs.com/frankcui/p/10468228.html
Copyright © 2011-2022 走看看