位运算 - 位运算的常见用法/题目

定理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)

 

posted on 2019-03-03 22:47  frank_cui  阅读(1052)  评论(0编辑  收藏  举报

导航

levels of contents