位运算题目小结
在刷Leetcode的时候发现许多位运算的题目,总结在一起看看能不能发现规律
78.子集
题目描述
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
1 | 输入: nums = [1,2,3] |
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/subsets
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
代码
这题其实属于排列组合小结里面总结过的一题,不过这里面解释另外一种思路,通过位运算的思路来解决。但是其实本质上还是排列组合里面的选取和不选取的思想。
举例:假设nums=[1,2,3,4],二进制的0可以写成0000,代表一个数也不取,1=0001表示去第一个数也就是[1],2=0010,表示取第二个数[2],3=0011表示取1和2位[1,2],4=0100表示[3]….15=1111表示[1,2,3,4]
1 | public List<List<Integer>> binaryBit(int[] nums) { |
这个代码有个问题,就是nums.length超过32就处理不了,不过这个目前不用担心(因为如果超过长度超过32那用其他方法进行子集提取,估计也会非常消耗时间),这题的测试用例并没有超过32位
136.只出现一次的数字
题目描述
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
1 | 输入: [2,2,1] |
示例 2:
1 | 输入: [4,1,2,1,2] |
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/single-number
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
代码
这题做过很多次了,可以使用哈希表进行处理,但是最好的方法是使用异或^进行处理,这里利用到了异或的一个特性,两个相同的数异或为0(相同为0不同为1)
1 | class Solution { |
137.只出现一次的数字II
题目描述
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
1 | 输入: [2,2,3,2] |
示例 2:
1 | 输入: [0,1,0,1,0,1,99] |
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/single-number-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
代码
这次是一个元素出现一次,其他元素出现三次。这题可以使用哈希表进行处理,但是位了训练位运算,我们采取使用位运算的思想处理。
这题看了题解之后,我想到了计算机组成原理里面的真值表法,所以我们可以使用该方法进行处理。
首先我们想到需要一个逻辑运算实现 1*1*1=01∗1∗1=0 且 01=10=10∗1=1∗0=1 ,其中 *为这种新逻辑操作符。根据这个,我们可以想到
出现0次为0,出现1次为1,出现2次的值无所谓,出现3次就又回到0,也就是说,我们一共需要记录3种状态:0次,1次,2次,之后次数都是这三种状态的循环。其实这也就是一个模三运算。
记录两个状态需要的是一位二进制0/1,那么记录三种状态需要的是至少两位二进制,可以是00, 01, 10, 11,这里我们只需要选其中任意三个状态即可,例如:00,01,10,分别代表0次1次2次。
用00代表0次,01代表出现1次是因为刚好对应数字原本那位上0代表0次,1代表1次,这样可以方便写程序,不这么选也可以,但最后你自己要有个映射规则。至于2次的编码无所谓,10和11都可以,反正最后答案也不要它,只是个中间状态,辅助我们计算的。
那么对于输入数字的每一位二进制位,都可以用这三种状态表示。如果再输入一个数字,对于每一位上,我们的操作可以化为:
新输入的是0(即00),三种状态都维持不变,00$\rightarrow $00,01$\rightarrow$01,10$\rightarrow$10
新输入的是1(即01),00$\rightarrow$01,01$\rightarrow$10,10$\rightarrow$00
所以可以得出真值表
| 当前状态XY | 输入Z | 输出X | 输出Y |
|---|---|---|---|
| 00 | 0 | 0 | 0 |
| 01 | 0 | 0 | 1 |
| 10 | 0 | 1 | 0 |
| 00 | 1 | 0 | 1 |
| 01 | 1 | 1 | 0 |
| 10 | 1 | 0 | 0 |
XY是指两位数表示的状态,因为1位只能表示2个状态。后面的XY表式输出后的状态,其实6个状态对应着前来说的输入为0和输入为1,比如第一行其实就是00*0$\rightarrow $00
根据真值表的求法这里,可以得到
然后我们可以根据布尔表达式化简一下
同理可得
所以答案可以是这样y
1 | class Solution { |
标准答案是这样,应该是最终化简后的结果
1 | class Solution { |
思考:最终为啥返回y而不是返回x
因为最终返回的是出现次数1次的数字,只有Y为1的时候才代表该数出现一次
191.位数1的个数
题目描述
编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。
示例 1:
1 | 输入:00000000000000000000000000001011 |
示例 2:
1 | 输入:00000000000000000000000010000000 |
示例 3:
1 | 输入:11111111111111111111111111111101 |
提示:
- 请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
- 在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在上面的 示例 3 中,输入表示有符号整数 -3。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/number-of-1-bits
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
代码
方法思路比较简单,就是n&(n-1)
1 | n = 0010 |
这样就取出了末尾的1
1 | public class Solution { |
201.数字范围按位与
题目描述
给定范围 [m, n],其中 0 <= m <= n <= 2147483647,返回此范围内所有数字的按位与(包含 m, n 两端点)。
示例 1:
1 | 输入: [5,7] |
示例 2:
1 | 输入: [0,1] |
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/bitwise-and-of-numbers-range
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
代码
其实就是找n和m的公共前缀
1 | class Solution { |
利用n&(n-1)
1 | class Solution { |
总结
总结一些位运算可能使用到的规律:
- 两个相同的数异或值为0,用于筛选出独特的数,eg:136.只出现一次的数字
- 有时候可以使用真值表法
- 使用n&(n-1)取出末尾的1