位运算 算法
-1.与: &
或: |
非: !
异或 : ^ 相同为 0, 不同为 1
int 32 位 -> int 的范围 -2^(31)to 2^ 31 - 1
long 64 位 -> long 的范围 -2^(63) to 2^ 63 -1
=========================================
-2.补码:
-x + 1 是 x 的 补码
2 + 1111111111111111111111111111110 = 0000....0
=========================================
-3. 1 << n = 2 ^ n
n >> x = n/ (2 ^ x )
=========================================
-4. 把array of char 转换成 String -> String.valueOf( arr)
-> new String(arr)
把 array of char 里的一个字符转换Case -> Character.toUpperCase( arr[i] )
-> Character.toLowerCase( arr[i] )
=================================================
-5. "<<" : 左移运算符,num << 1,相当于num乘以2
">>": 右移运算符,num >> 1,相当于num除以2 (如果是负数补位是“1”, 正数补位是“0”)。
">>>": 无符号右移,忽略符号位,空位都以0补齐
=================================================
-6. 二进制状态压缩
操作: 运算:
取出整数 n 在二进制下的第 k 位 ----> (n >> k) & 1
取出整数 n 在二进制表示下的第 0 ~ k - 1 位(后k位)---> n &((1 << k) -1)
把整数 n 在二进制表示下的第 k 位取反 ---> n ^( 1 << k)
把整数 n 在二进制表示下的第 k 位赋值1 ---> n | ( 1<< k)
把整数 n 在二进制表示下的第 k 位赋值0 ---> n &( ~( 1 << k ))
题目类型总结:
1.直接问二进制数的相关操作(翻转, 补码,加法, 计算1bit 的总数)
2.字符串的a到z, 或者 A到Z 的matching(前缀树,字符串共同字符)
3.要操作的数字都是2的倍数(判断指数是2或3或4, 二进制表(时间),快速幂算法)
4.寻找二进制数的规律(判断alerting bits, )
1· 二进制中1 的个数(15 剑指offer )
- 用 n = n & (n -1) 来检测有几个 1 在 二进制数中, 时间复杂度 ,有几个 1 s loop 就运行几次
public static int NumberOf1(int n) {
int count = 0;
while( n ) {
count ++;
n = (n- 1) & n;
}
return count;
}
2· 数值的整数次方(16 剑指offer )
- 用递归的形式, 求解
public static double PowerWithExp(double base, int exponent){
if(exponent == 1)
return base;
if(exponent == 0)
return 1;
double result = PowerWithExp( base, exponent >> 1 );
result * = result;
if((exponent & 0x1)== 1)
result *= base;
return result;
}
- 用 loop 的形式,求解
public static int int PowerExp(int base, int exponent) {
int res = 1, tmp = base;
while(exponent != 0) {
if( ( exponent & 0x1) == 1)
res = res * tmp;
tmp *= tmp;
exponent >>= 1;
}
return res;
}
3· 算法进阶 a^b % p
-
取模运算的性质
(a + b)% p = (a % p + b % p) % p
(a - b) % p = ( a % p - b % p) % p
(a ^ b) % p = ( ( a % p)^ b) % p
(a * b) % p = ( a % p * b % p ) % p -
long (占 8个 字节)数据范围变为:-263~263-1 ; int(占4个字节) 数据范围变为:-2^31~ 2^31-1
-
快速幂算法: 把复杂度降到了 log b 次
3 * 3 * 3......* 3
3 ^ 7 = ?
3 ^ 1 = 3
3 ^ 2 = 9
3 ^ 4 = 81
3 ^ 1000000
3 ^ 1 = 3
3 ^ 2 = 9
3 ^ 4 = 81
3 ^ 8
3^( 2 ^ 19)=
2^k 次 3 的相乘
import java.util.*;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int a = in.nextInt() , b = in.nextInt(), c = in.nextInt();
long res = 1 % p;
long tmp = a;
for(; b!= 0; b >> = 1) {
if( (b & 1 ) == 1) {
/* ( (a * b) % p = a % p * b % p ) % p */
res = res * tmp % p ;
}
tmp = tmp * tmp % p ;
}
System.out.println( res);
}
4· 算法进阶 a * b % p
- 因为有时候当 a 和 b 的乘积 超过 - 2^ 63 to 2 ^ 63 -1; 就无法得到 a * b % p, 所以这里也需要用到 快速幂算法
a * b
a + a + a + a + .... + a
a * 1 = a
a * 2 = 2a
a * 4 = 4a
a * 8 = 8a
.....
a * ( 2 ^ k ) = 2 ^ k * a 就是 2^k 次 a 相加
时间复杂度: log b 次
import java.util.*;
public static void main(String [] args) {
Scanner in= new Scanner (System.in);
long a = in.nextInt(), b = in.nextInt(), c = in.nextInt();
long tmp = a;
long res = 0 % p;
while( b != 0) {
if( ( b & 1) == 1)
res = (res + tmp ) % p;
tmp = (tmp + tmp) % p;
b >> = 1;
}
return res;
}
5· Sum of two Integer(371 leetcode)
- 用 ^ 和 & 来完成 加法运算
public static int getSum(int a, int b) {
return b == 0 ? a : getSum( a^b, (a & b )<< 1);
}
6·Single Number II (137 leetcode)
- 解题思路 把 所有element 以二进制的形式 存入array 然后 mod 3. array 里留下来的数, 就是结果。
class Solution {
public static int singleNumber(int[] nums) {
int[] res = new int[32];
for(int i=0; i<nums.length; i++){
int tmp = nums[i];
for(int j=res.length -1 ; j >=0 ;j--){
res[j] += tmp & 0x1;
tmp >>= 1;
}
}
for(int i =0; i<res.length; i++){
res[i] = res[i] % 3;
}
int a = 0;
for(int i=0 ; i< res.length ; i++){
a <<= 1;
a += res[i];
}
return a;
}
}
- 思路二:
public int singleNumber(int[] nums) {
int one = 0, two = 0;
for(int i : nums) {
one = (one ^ i) & ~two;
two = (two ^ i) & ~one;
}
return one;
}
7 · Power of Two(231 leetcode)
- 如果一个数的指数是2, 那么可以确定的是它的 二进制表示是只有最高位是 1,其它位都是0.所以可以利用这一特性。如果n & (n - 1) == 0,那么可以确定这个数的二进制表示是100000000,也就是2的指数。然后考虑边界情况, 例如 n>0
class Solution {
public boolean isPowerOfTwo(int n) {
return n >0 && ( (n &( n-1) )== 0);
}
}
8 · Power of Four(342 leetcode)
- 如果一个数的指数是4, 那么第一可以确定它的 二进制表示是只有最高位是 1,其它位都是0。 可以用上面 power of two 的方法用 ( n & (n -1)) == 0. 然后 如果是指数为4的话,所以1 bit 永远在 odd 的位置 0101。 所以 0X55555555 可以get rid of those power of 2.
public boolean isPowerOfFour(int num) {
return num > 0 && (num &(num -1) == 0 ) && ((num & 0x55555555) == num );
}
9 · Power of Three (326 leetcode)
- 解题思路 这里只能用常规的解题思路, 用loop 循环 因为3 不是2 的倍数, 不断取余,直到取余结果不等于0, 就结束loop。当结果不为1时,就说明 n 不是 power of three。
public static boolean isPowerOfThree(int n) {
if(n <= 0)
return false;
while( n % 3 == 0) {
n /= 3;
}
return n == 1;
10 · Bitwise AND of Numbers Range (201 leetcode)
- last bit of ( odd number & even number) is 0
- 2.when m != n, there is at least an odd number and even number , so the last bit position result is 0.
- move m , n to right position 直到 m == n, 因为当 m != n 时, 说明中间必然存在 odd number 和 even number 说明一定是0, 当 m == n 时,可以keep 当前 bits
public int rangeBitwiseAnd(int m, int n) {
if( m == 0)
return 0;
int moveFactor = 0 ;
while( m != n) {
m >>= 1;
n >>=
}
}
11 · Maximum XOR of Two Numbers in an Array (421 leetcode)
- 这道题使用到了 Trie 树(前缀树 或者 字典树)+ 分治法
1.Trie 的强大之处就在于它的时间复杂度。它的插入和查询时间复杂度都为 o(K), 其中 K 为key 的长度, 与Trie中保存了多少个元素无关。
2.字典树(Trie)可以保存一些字符串-> 值的对应关系。基本上,它跟Java 的HashMap 功能相同,都是 key-value 映射,只不过Trie 的key 只能是字符串。
3. Trie 树,又称单词查找树或键树,是一种树形结构。典型应用 是用于统计和排序大量的字符串,所以经常被搜索引擎系统用于文本词频统计。 它的优点是: 最大限度地减少无谓的字符串比较,查询效率比哈希表高。
4. Trie 的核心思想是空间换时间。 利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。 - 解题思路 根据题目描述, 我们需要找到最大的异或值,异或代表了两个数的二进制的不同程度,且越高位越不一样,异或值就越大,由于是按位比较,所以使用Trie 树来当做基础数据结构。
我们可以总结出以下几点:
1.因为整型的位数是固定的,排除第一位符号位,Trie树的高度是常数的,即最高32层(包括root)。
2. 由于只有0 和 1 两个子节点,所以为了节省空间,可以使用 二叉树 的方式(或者数组或 HashMap 均可)
3. 由于是异或,前缀为如果相同,异或值都是0,所以可以先找到第一个 两个字节都不为空的节点当做root。
class Solution{
class TrieNode{
int val;
TrieNode zero, one;
boolean isEnd;
}
class TrieTree{
TrieNode root;
public TrieTree() {
root = new TrieNode();
}
public void insert(int num){
TrieNode cur = root;
int j = 1 << 30;
for(int i=0; i<31; i++){
/*根据 num 在 j 位置的数目判断应该向 0 还是向 1 */
int b = num & j == 0 ? 0 : 1 ;
TrieNode node = new TrieNode();
if( b == 1 && cur.one == null )
cur.one = new TrieNode();
if( b == 0 && cur.zero == null )
cur.zero = new TrieNode();
cur = b == 0 ? cur.zero : cur.one;
j >> 1;
} /* for loop */
cur.isEnd = true;
cur.val = num;
}
public int findMaximumXOR(int[] nums) {
if( nums == null || nums.length <= 1)
return 0;
TrieTree tree = new TrieTree();
for(int val : nums){
tree.insert(val);
}
/* 获取真正需要开始判断的root */
TrieNode cur = tree.root;
while( cur.one == null || cur.zero == null) {
cur = cur.zero == null ? cur.one : cur.zero;
}
return maxHelper( cur.zero , cur.one )
}
/* 分治法, 原则就是使两个分支的高位不同
1. 当1分支的下一位只有1时,找 0 分支的0, 若没有就找1;
2. 当1分支的下一位只有0 时,找 0 分支的1, 若没有就找0;
3. 当1分支的下一位有0 和 1时, 看0 分支, 如果0 分支只有 1 ,则1分支走1,反之走0.
4. 当0,1两个分支均有两个下一位时,尝试【1分支走1,0分支走0】和【1分支走0,0分支走1】两条路线并取最大值
*/
public int maxHelper(TrieNode zero , TrieNode one) {
if(zero.isEnd && one.isEnd)
return zero.val ^ one.val;
if( one.zero == null)
return maxHelper( one.one, zero.zero != null ? zero.zero : zero.one );
else if(one.one == null)
return maxHelper( one.zero , zero.one != null ? zero.one : zero.zero);
else if(zero.zero == null)
return maxHelper( one.zero, zero.one);
else if (zero.one == null)
return maxHelper( one.one, zero.zero);
else
return Math.max( maxHelper( zero.one , one.zero ), maxHelper( zero.zero, one.one ));
}
}
}
12 · Number Complement (476 leetcode)
- 解题思路 从num 的最低位一步步 遍历到最高位, 通过 或 mask 变量。
public int findComplement(int num) {
if(num == 0)
return 1;
int mask = 1;
int ret = 0;
while( num != 0) {
if( num & 1 == 0 )
ret |= mask;
mask <<= 1;
num >>= 1;
}
return ret;
}
13 · Missing Number (268 leetcode)
- 解题思路 使用连加公式, 1+2+3+...+ 100 -> (上底 + 下底) * 高 / 2 -> (1 + 100)* 100 /2
public int missingNumber(int[] nums) {
if(nums == null)
return 0;
int sum = (nums.length + 1)*nums.length/2;
for(int val : nums)
sum -= val;
return sum;
}
14 · Maximum Product of Word Lengths(318. leetcode)
- 解题思路 把每个string 都转换成 一个 二进制表示的int 值, 如果 两个 二进制表示的int 值 相互 “ & ”的结果是0 , 则表示 字符串没有重复的 ;a 到 z 的 26个字母 可以全部用 1<<( char - 'a'), 来表示 因为int 有 32 位, 而 a 到 z 里面 z 需要向左位移25位,因为 32 > 25 所以正好能被全部cover。
public int maxProduct(String[] words) {
if(words == null)
return 0;
int[] value = new int[words.length];
for(int i =0; i< words.length ; i++) {
for(int j = 0; i < words[i].length; j++) {
value[i] |= 1 << (words[i].charAt(j) - 'a');
}
}
int max = 0 ;
int tmp = 0 ;
for(int i =0 ; i<value.length; i++) {
for(int j = i+1 ; j < value.length; j++) {
if( (value[i] & value[j]) == 0) {
tmp = words[i].length() * words[j].length();
max = max > tmp ? max : tmp;
}
}
}
return max;
}
15 · Reverse Bits(190. leetcode)
- **解题思路 ** 这一题与第14 题, 的差别是第14题是求补码, 把32 位都考虑在内, 但是 这道必须注意 一定要 向左移满 32位
public int reverseBits(int n) {
int ret = 0;
for(int i=0; i<32; i++) {
ret |= n & 1;
n >>= 1;
if (i < 31)
ret <<= 1;
}
return ret;
}
16 · Binary Watch (401. leetcode)
这道题不是很明显的 bit manipulation 的类型问题
-
1.需要列出所有时间组合的可能性
-2.所有可能性里面要剔除 不符合条件的组合例如 h > 11或 minutes > 59
-3. 输出的string format 要注意。 -
java 语法注意点 创建List或者Map的时候指定类型与不指定类型有什么区别
-例:List list = new ArrayList();
List<String> list = new ArrayList<String>();
创建一个Map
Map map = new HashMap();
Map<String,String> map = HashMap<String,String>(); -
其实指定了参数类型编译器在编译期间就会帮助你检查存入容器的对象是不是参数类型!不是就会报错!保证了类型安全!性能上没什么影响,因为泛型在运行期间会擦除!就是说用不用类型参数在运行期间编译后的运行代码是一样的!
-
解题思路: 运用*backtracking *的解法
1.判断的base case 终止条件: hour 超过 11, minutes 超过 59
2.当hour 和 minutes 符合条件时,把当前结果加入到 list 里面。
3.hour: 8,4,2,1 ; minutes:32,16,8,4,2,1 都是以2为指数可以用 1<< i 表示
public List<String> readBinaryWatch(int num) {
List res = new ArrayList<String>();
if(num == null)
return res;
generate(num, res, 0,0,0,0);
return res;
}
public void generate(int n, List<String> res, int hour, int minutes, int i, int j) {
if(hour > 11 || minutes > 59) return;
if(n == 0)
res.add( hour+":" + minutes>10? "": "0" + minutes);
else{
while(j < 6){
generate( n-1, res, hour, minutes+ 1 << j, i, j+1);
j++;
}
while(i < 4){
generate( n-1, res, hour + 1<< i , minutes, i+1, j);
i++;
}
} /*else */
}
16 · Integer Replacement(397. leetcode)
- 1.当 n 是 even 的时候, 需要进行的操作是 fixed, 问题就在于 当 n 是odd 的时候,需要判断是 n+1 或是 n-1 。 虽然 backtracking 也是一种解法,但是时间复杂度较高, 所以这里有另外一种更好的解法。
-解题思路: 当 n 是 odd 时,n = 2k + 1, n+1 = 2k+2, n-1 = 2k . Then (n+1)/2 = k+1, (n-1)/2 = k. 所以其中有一个是奇数, 另一个是偶数。 我们的目标是 尽可能的除以2, 所以我们要在 k 和 k+1 中选择 偶数, 所以我们要 除以4。 但这里有个special case n = 3, 我们要选择 n-1 而不是 n+1
public int integerReplacement(int n) {
if( n == Integer.MAX_VALUE)
return 32;
int ret = 0 ;
while( n != 1) {
if( ( n & 1) == 0)
n >>= 1;
else {
if( n == 3 || (n -1 ) % 4 == 0) n--;
else {
n++;
}
}
ret ++;
}
return ret;
}
17·Binary Number with Alternating Bits (693.leetcode)
-
解题思路: 采用 “ ^” 的特性 相同为0 不同为 1, 让 d 在 0 与 1 之间相互切换. 若 d ^= 1 ,
当 d = 0, d ^= 1 -> d =1;
当 d = 1, d^= 1 -> d = 0; - 0 ^ 0 = 0
- 1 ^ 0 = 1;
public boolean hasAlternatingBits(int n) {
int d = n & 1;
while( (n & 1) == d) {
d ^= 1;
n >>= 1;
}
return n == 0;
}
- 方法二 : 因为 “ ^ ” 之后结果全为1 的话, 说明每一位都不一样 所以结果就都是1, 然后 在return的时候检查 a==1111, a+1=10000, a&(a+1)==0。
public boolean hasAlternatingBits(int n) {
int a = ( n ^ ( n >> 1) );
return ( a & ( a + 1)) == 0;
}
18· IP to CIDR (751.leetcode)
-
解题思路:
-由于题目中要求尽可能少的使用CIDR块,那么在n确定的情况下,CIDR块能覆盖的越多越好。根据我们前面的分析,当CIDR块斜杠后面的数字越小,该块覆盖的ip地址越多。那么就是说相同前缀的个数越少越好,但是我们的ip地址又不能小于给定的ip地址,所以我们只能将0变为1,而不能将1变为0。 -
解题目标:
-1.用 long 来存放32 位的二进制数
-2.找到二进制数的末尾的1的位置,因为只能把0变1,不能把1变0
-3.得到能潜在覆盖的个数是否小于或等于要求覆盖的个数
-4.得到当前的start ip 地址 能够覆盖的地址,然后转换成 CIDR 的形式
public List<String> ipToCIDR(String ip, int range) {
long x = 0;
List<String > list = new ArrayList<String>();
String[] ips = ip.split(".");
/* 把32位的二进制放到 long 里面 */
for(int i = 0; i < ips.length; i++) {
x = Integer.parseInt( ips[i]) + ( x << 8);
}
while( range > 0) {
long step = x & -x;
while(step > range) step >>= 1;
list.add( ipToString(x, (int) step) );
x += step;
range -= step;
}
return list;
}
public String ipToString( long x, int step) {
int[] ans = new int[4];
for(int i=0; i< ans.length; i++) {
ans[i] = x & 255;
x >>= 8;
}
int len = 32;
while(step > 1) {
step >>= 1;
len --;
}
return ans[3] +"."+ ans[2] +"."+ ans[1] +"."+ ans[0] +"\"+len;
}
19. Letter Case Permutation (784.leetcode)
-
解题思路:
-BFS:用一个queue 来实现 BFS
public List<String> letterCasePermutation(String S) {
if(S == null) return new LinkedList<String>();
Queue<String> queue = new LinkedList<String>();
queue.offer(S);
for(int i=0; i< S.length(); i++) {
if( Character.isDigit(S.charAt(i))); continue;
int size = queue.size();
for(int j =0; j<size;j++) {
String cur = queue.poll();
int[] curArr = cur.tocharArray();
curArr[i] = Character.toUpperCase(curArr[i]);
queue.add(String.valueOf(curArr));
curArr[i] = Character.toLowerCase(currArr[i]);
queue.add(String.valueOf(curArr));
}
}
return new LinkedList<>(queue);
}
-
解题思路:
-DFS
public List<String> letterCasePermutation(String S) {
if(S == null ) return null;
List<String> res = new ArrayList<String>();
char[] cur = S.toCharArray();
helper(res, S, 0 );
return res;
}
public void helper(List<String> res , char[] cur, int pos ) {
if(pos == S.length) {
res.add( new String(cur));
return;
}
if( Character.isDigit( cur[pos] )) {
helper( res, cur, pos + 1);
return;
}
cur[pos] = Character.isLowerCase(cur[pos]);
helper(res, cur, pos +1 );
cur[pos] = Character.isUpperCase(cur[pos]);
helper(res, cur, pos + 1);
}
20. Bitwise ORs of Subarrays (898.leetcode)
- array 里的元素,相互组合得到OR 的结果,最后output 有几个不同结果。这里是
-解题思路:
-1.只能0变1 , 不能1变0,因为是' OR'
public int subarrayBitwiseORs(int[] A) {
Set<Integer> ans = new HashSet();
Set<Integer> cur = new HashSet();
cur.add(0);
for(int x : A) {
Set<Integer> cur2 = new HashSet();
for(int y : cur) {
cur2.add( x | y);
}
cur2.add(x);
cur = cur2;
ans.addAll( cur);
}
return res;
}
21. Prime Number of Set Bits in Binary Representation (762.leetcode)
-解题思路:因为所有的数字都是用32个bits 表示,通过计算有几个数的bits 个数是prime number。 所以bits 个数会在[0, 32] 区间内。[0,32] 的区间内 2,3,5,7,9,11,13,17,19,23,29 是prime number。所以可以把这个映射在bits的位置上 0010100010100010101100 -> 665772 (十进制)
2nd,3rd,5th,7th,11th,13th,17th,19th,23rd and 29th bits are 1。
-注意的细节
-1.>> 的优先级 高于 &
-2.L++ 是先算L,然后再++
-3.查看一个小于32 的数字是否存在,可以用一下方法
public int countPrimeSetBits(int L, int R) {
int res = 0;
while(L <= R) {
res += 665772 >> Integer.bitCount(L++) &1;
}
return res;
}
22.Convert a Number to Hexadecimal (405.leetcode)
-主要解决问题:
-1. 把32 位的bits, 拆分到array 里面
-2. 把 array of char 用正确的顺序放到string里面
-3. 把 [10, 15] 隐射到[a,f]
-4.这里最好用 >>> 而不是 >>, 因为当 num 是负数的时候,>> 的补位永远是“1”, 所以while loop 永远不会结束。
public String toHex(int num) {
char [] map = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
String str = "";
while(num >0) {
int index = num & 15;
str = map[index] + str;
num >>>= 4;
}
return str;
}
23.Total Hamming Distance(477.leetcode)
-解题思路:
-这道题提别的做法是,一共是 1 到32 位,,首先 遍历n 个 元素, 之后记录每位有多少个1。这就意味着, 在每一位上有k个1 bit,就说明有 (n-k)个 是在这一位是不一样的, 所以在每一位上就有 k *( n - k) 个 hamming distance 。
public int totalHammingDistance(int[] nums) {
int total = 0;
int mask = 1;
int bitCount = 0;
for(int i = 0; i<30;i++) {
mask <<= i;
for(int val : nums)
if((val & mask) == 1) bitCount++;
total += bitCount * ( nums.length - bitCount);
bitCount =0;
}
return total;
}
24. Valid Word Abbreviation(408.leetcode)这一题和之后的2题是follow up
-.在比较两个string 的pattern的时候,例如和数字之间的pattern, 可以用O(n)解决。 一个是 detail string , 还有一个是 abbrev string
-解题思路: 直接 遍历其中的abbev string , 然后一一对照word string 检查。
public boolean validWordAbbreviation(String word, String abbr) {
int n = word.length(), index =0, num=0;
for(int i=0; i<abbr.length(); i++) {
char ch = abbr.charAt(i);
if( Character.isDigit(ch)) {
if( ch == '0' && (i ==0 || !Character.isDigit(abbr.charAt(i-1))))
return false;
num = num * 10 + (ch - '0');
}
else{
index += num;
num =0;
if(index >= word.length() || word.charAt(index) != ch)
return false;
index ++;
}
index += num;
return index == n;
}
}
25. Generalized Abbreviation( 320.leetcode)这一题是medium 难度[ 这题还是不太明白]
class Solution {
public List<String> generateAbbreviations(String word){
List<String> ret = new ArrayList<String>();
backtrack(ret, word, 0, "", 0);
return ret;
}
private void backtrack(List<String> ret, String word, int pos, String cur, int count){
if(pos==word.length()){
if(count > 0) cur += count;
ret.add(cur);
}
else{
backtrack(ret, word, pos + 1, cur, count + 1);
backtrack(ret, word, pos+1, cur + (count>0 ? count : "") + word.charAt(pos), 0);
}
}
}
网友评论