LeetCode 位运算
基础部分
基本原理
0s 表示一串 0,1s 表示一串 1。
x ^ 0s = x x & 0s = 0 x | 0s = x
x ^ 1s = ~x x & 1s = x x | 1s = 1s
x ^ x = 0 x & x = x x | x = x
利用 x ^ 1s = ~x 的特点,可以将一个数的位级表示翻转;利用 x ^ x = 0 的特点,可以将三个数中重复的两个数去除,只留下另一个数。
1^1^2 = 2
利用 x & 0s = 0 和 x & 1s = x 的特点,可以实现掩码操作。一个数 num 与 mask:00111100 进行位与操作,只保留 num 中与 mask 的 1 部分相对应的位。
01011011 &
00111100
--------
00011000
利用 x | 0s = x 和 x | 1s = 1s 的特点,可以实现设值操作。一个数 num 与 mask:00111100 进行位或操作,将 num 中与 mask 的 1 部分相对应的位都设置为 1。
01011011 |
00111100
--------
01111111
位与运算技巧
n&(n-1) 去除 n 的位级表示中最低的那一位 1。例如对于二进制表示 01011011,减去 1 得到 01011010,这两个数相与得到 01011010。
01011011 &
01011010
--------
01011010
n&(-n) 得到 n 的位级表示中最低的那一位 1。-n 得到 n 的反码加 1,也就是 -n=~n+1。例如对于二进制表示 10110100,-n 得到 01001100,相与得到 00000100。
10110100 &
01001100
--------
00000100
n-(n&(-n)) 则可以去除 n 的位级表示中最低的那一位 1,和 n&(n-1) 效果一样。
移位运算
>> n 为算术右移,相当于除以 2n,例如 -7 >> 2 = -2。
11111111111111111111111111111001 >> 2
--------
11111111111111111111111111111110
>>> n 为无符号右移,左边会补上 0。例如 -7 >>> 2 = 1073741822。
11111111111111111111111111111001 >>> 2
--------
00111111111111111111111111111111
<< n 为算术左移,相当于乘以 2n。-7 << 2 = -28。
11111111111111111111111111111001 << 2
--------
11111111111111111111111111100100
mask 计算
要获取 111111111,将 0 取反即可,~0。
要得到只有第 i 位为 1 的 mask,将 1 向左移动 i-1 位即可,1<<(i-1) 。例如 1<<4 得到只有第 5 位为 1 的 mask :00010000。
要得到 1 到 i 位为 1 的 mask,(1<<i)-1 即可,例如将 (1<<4)-1 = 00010000-1 = 00001111。
要得到 1 到 i 位为 0 的 mask,只需将 1 到 i 位为 1 的 mask 取反,即 ~((1<<i)-1)。
Java 中的位操作
static int Integer.bitCount(); // 统计 1 的数量
static int Integer.highestOneBit(); // 获得最高位
static String toBinaryString(int i); // 转换为二进制表示的字符串
不使用额外变量交换两个数
a = a ^ b;
b = a ^ b;
a = a ^ b;
461. 汉明距离
简单
两个整数之间的汉明距离指的是这两个数字对应二进制位不同的位置的数目。
给出两个整数 x
和 y
,计算它们之间的汉明距离。
注意:
0 ≤ x
, y
< 231.
示例:
输入: x = 1, y = 4
输出: 2
解释:
1 (0 0 0 1)
4 (0 1 0 0)
↑ ↑
上面的箭头指出了对应二进制位不同的位置。
class Solution {
public int hammingDistance(int x, int y) {
int res = 0, a = (int)Math.pow(2,30);
while (a > 0){
if ((x&a)!=(y&a)) res++;
a >>= 1;
}
return res;
}
}
class Solution {
public int hammingDistance(int x, int y) {
return Integer.bitCount(x ^ y);
}
}
136. 只出现一次的数字
简单
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,1]
输出: 1
示例 2:
输入: [4,1,2,1,2]
输出: 4
class Solution {
public int singleNumber(int[] nums) {
int res = 0;
for (int num : nums) res ^= num;
return res;
}
}
268. 缺失数字
简单
给定一个包含 0, 1, 2, ..., n
中 n 个数的序列,找出 0 .. n 中没有出现在序列中的那个数。
示例 1:
输入: [3,0,1]
输出: 2
示例 2:
输入: [9,6,4,2,3,5,7,0,1]
输出: 8
说明:
你的算法应具有线性时间复杂度。你能否仅使用额外常数空间来实现?
class Solution {
public int missingNumber(int[] nums) {
int res = nums.length;
for (int i = 0; i < nums.length; i++){
res ^= i ^ nums[i]; //res += i - nums[i];
}
return res;
}
}
260. 只出现一次的数字 III
中等
给定一个整数数组 nums
,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。
示例 :
输入: [1,2,1,3,2,5]
输出: [3,5]
注意:
- 结果输出的顺序并不重要,对于上面的例子,
[5, 3]
也是正确答案。 - 你的算法应该具有线性时间复杂度。你能否仅使用常数空间复杂度来实现?
class Solution {
public int[] singleNumber(int[] nums) {
int mask = 0;
for (int num : nums) mask ^= num;
int a = 1;
while ((mask & a) == 0) a <<= 1;
int x = 0, y = 0;
for (int num : nums){
if ((num & a) == 0){
x ^= num;
}else {
y ^= num;
}
}
return new int[]{x, y};
}
}
190. 颠倒二进制位
简单
颠倒给定的 32 位无符号整数的二进制位。
示例 1:
输入: 00000010100101000001111010011100
输出: 00111001011110000010100101000000
解释: 输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596,
因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000。
示例 2:
输入:11111111111111111111111111111101
输出:10111111111111111111111111111111
解释:输入的二进制串 11111111111111111111111111111101 表示无符号整数 4294967293,
因此返回 3221225471 其二进制表示形式为 10111111111111111111111111111111 。
提示:
- 请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
- 在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在上面的 示例 2 中,输入表示有符号整数
-3
,输出表示有符号整数-1073741825
。
public class Solution {
public int reverseBits(int n) {
int res = 0;
for (int i = 0; i < 32; i++){
res <<= 1;
res += n & 1;
n >>>= 1;
}
return res;
}
}
231. 2的幂
简单
给定一个整数,编写一个函数来判断它是否是 2 的幂次方。
class Solution {
public boolean isPowerOfTwo(int n) {
return n > 0 && Integer.bitCount(n) == 1;
// return n > 0 && (n & (n - 1)) == 0;
}
}
342. 4的幂
简单
给定一个整数 (32 位有符号整数),请编写一个函数来判断它是否是 4 的幂次方。
class Solution {
public boolean isPowerOfFour(int num) {
if (num < 0 || Integer.bitCount(num) != 1) return false;
int res = 0;
while (num > 0){
if ((num & 1) == 1) res++;
num >>= 2;
}
return res == 1;
}
}
693. 交替位二进制数
简单
给定一个正整数,检查他是否为交替位二进制数:换句话说,就是他的二进制数相邻的两个位数永不相等。
示例 1:
输入: 5
输出: True
解释:
5的二进制数是: 101
示例 2:
输入: 7
输出: False
解释:
7的二进制数是: 111
示例 3:
输入: 11
输出: False
解释:
11的二进制数是: 1011
示例 4:
输入: 10
输出: True
解释:
10的二进制数是: 1010
class Solution {
public boolean hasAlternatingBits(int n) {
int high = Integer.highestOneBit(n);
return helper(n, high, 1);
}
private boolean helper(int n, int high, int bit){
if (high == 0) return true;
int digit = (n&high) > 0 ? 1 : 0;
if (digit == bit) return helper(n, high>>1, bit^1);
return false;
}
}
476. 数字的补数
简单
给定一个正整数,输出它的补数。补数是对该数的二进制表示取反。
示例 1:
输入: 5
输出: 2
解释: 5 的二进制表示为 101(没有前导零位),其补数为 010。所以你需要输出 2 。
示例 2:
输入: 1
输出: 0
解释: 1 的二进制表示为 1(没有前导零位),其补数为 0。所以你需要输出 0 。
注意:
- 给定的整数保证在 32 位带符号整数的范围内。
- 你可以假定二进制数不包含前导零位。
class Solution {
public int findComplement(int num) {
int high = Integer.highestOneBit(num);
while (high > 0){
num ^= high;
high >>= 1;
}
return num;
}
}
371. 两整数之和
简单
不使用运算符 +
和 -
,计算两整数 a
、b
之和。
示例 1:
输入: a = 1, b = 2
输出: 3
示例 2:
输入: a = -2, b = 3
输出: 1
class Solution {
public int getSum(int a, int b) {
int res = a;
while (b != 0){
res = a ^ b;
b = (a & b) << 1;
a = res;
}
return res;
}
}
318. 最大单词长度乘积
中等
给定一个字符串数组 words
,找到 length(word[i]) * length(word[j])
的最大值,并且这两个单词不含有公共字母。你可以认为每个单词只包含小写字母。如果不存在这样的两个单词,返回 0。
示例 1:
输入: ["abcw","baz","foo","bar","xtfn","abcdef"]
输出: 16
解释: 这两个单词为 "abcw", "xtfn"。
示例 2:
输入: ["a","ab","abc","d","cd","bcd","abcd"]
输出: 4
解释: 这两个单词为 "ab", "cd"。
示例 3:
输入: ["a","aa","aaa","aaaa"]
输出: 0
解释: 不存在这样的两个单词。
class Solution { // 朴素方法,368ms
public int maxProduct(String[] words) {
Set<Character> set = new HashSet<>();
int res = 0;
for (int i = 0; i < words.length-1; i++){
for (char ch : words[i].toCharArray()) set.add(ch);
for (int j = i+1; j < words.length; j++){
boolean can = true;
for(char ch : words[j].toCharArray()){
if (set.contains(ch)){
can = false;
break;
}
}
if (can) res = Math.max(res,words[i].length()*words[j].length());
}
set.clear();
}
return res;
}
}
class Solution { // 24<32,所以可以用二进制位存字母,8ms
public int maxProduct(String[] words) {
int len = words.length;
int[] nums = new int[len];
for (int i = 0; i < len; i++){
int fill = 0;
for (char ch : words[i].toCharArray())
fill |= 1 << (ch - 'a');
nums[i] = fill;
}
int res = 0;
for (int i = 0; i < len-1; i++)
for (int j = i+1; j < len; j++)
if ((nums[i] & nums[j]) == 0)
res = Math.max(res,words[i].length()*words[j].length());
return res;
}
}
338. 比特位计数
中等
给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。
示例 1:
输入: 2
输出: [0,1,1]
示例 2:
输入: 5
输出: [0,1,1,2,1,2]
进阶:
- 给出时间复杂度为O(n*sizeof(integer))的解答非常容易。但你可以在线性时间O(n)内用一趟扫描做到吗?
- 要求算法的空间复杂度为O(n)。
- 你能进一步完善解法吗?要求在C++或任何其他语言中不使用任何内置函数(如 C++ 中的 __builtin_popcount)来执行此操作。
class Solution {
public int[] countBits(int num) {
int[] res = new int[num+1];
for (int i = 1; i <= num; i++)
res[i] = res[i&(i-1)] + 1; //res[i] = Integer.bitCount(i);
return res;
}
}
频率排序
411,393,260,898,421