Algorithms: Bit Manipulation(位操作)

位操作在编程语言中,支持以下四种逻辑操作:与(&), 或(|), 非(~)和异或(^)。而在Java当中,移位操作包括:<< 左移, >> 带符号右移, >>>忽略符号位,空位补0.


注意,在Java中,& > ^ > | 位运算符的优先级是比 ==, !=两个逻辑运算符低。但比&&和||高。所以记得加括号,必须要的时候。


位运算常用的套路:


1) 设置一个位:A |= 1 << 第几位。

       (A >> x) & 1 用来检查某一个位是不是1. 

       | 可以拿来给1左右两边的所有bit都赋1


例题:

190. Reverse Bits


/**
Reverse bits of a given 32 bits unsigned integer.

For example, given input 43261596 (represented in binary as 00000010100101000001111010011100), return 964176192 (represented in binary as 00111001011110000010100101000000).
注意两点:
1)&的优先级比==低。
2)二进制32位是0-31次方. unsign的范围是0 - 2 ^ 32 - 1. 所以,1最多左移31位。
3)注意使用无符号右移。
*/
public class Solution {
    // you need treat n as an unsigned value
    public int reverseBits(int n) {
        int res = 0;
        for (int i = 1; i <= 32 && n != 0; ++i) {
            if ((n & 1) == 1) {// & 用来select a certain bit. 看第四点
                res |= 1 << 32 - i;
            }
            n = n >>> 1;
        }
        return res;
    }
}

这道题还有另外一个解法,注意,当题目注明是unsigned的时候,要使用无符号右移。下面这个解法要好好体会下用&作mask操作的妙用:


public class Solution {
    // you need treat n as an unsigned value
    public int reverseBits(int x) {
        x = ((x & 0xaaaaaaaa) >>> 1) | ((x & 0x55555555) << 1);
        x = ((x & 0xcccccccc) >>> 2) | ((x & 0x33333333) << 2);
        x = ((x & 0xf0f0f0f0) >>> 4) | ((x & 0x0f0f0f0f) << 4);
        x = ((x & 0xff00ff00) >>> 8) | ((x & 0x00ff00ff) << 8);
        x = ((x & 0xffff0000) >>> 16) | ((x & 0x0000ffff) << 16);
        return x;
    }
}

第一步,提取奇偶交换。

第二步,提取4位中的左两位和右两位。

第三步,提取8位的左4位和右4位。

一直到32位的左16和右16. 


例题:169 Majority Element


Given an array of size n, find the majority element. The majority element is the element that appears more than ⌊ n/2 ⌋ times.


You may assume that the array is non-empty and the majority element always exist in the array.


class Solution {
	// Way1 sort
	public int majorityElement(int[] nums) {
	Arrays.sort(nums);
	return nums[nums.length / 2];
	}
	
	// Way2 HashMap count
	public int majorityElement(int[] nums) {
		Map<Integer, Integer> count = 
				new HashMap<>();
		int n = nums.length;
		for (int num : nums) {
			if (count.containsKey(num) == false) {
				count.put(num, 1);
			} else {
				count.put(num, count.get(num) + 1);
			}
			if (count.get(num) > (int)(n / 2)) {//注意,上述的if和else的情况都要检测,以防只有一个元素
				return num;
			}
		}
		return -1;// if not. 
	}
	
	// Way 3 Moore vote. 
	/**
	 * 这个算法比较独特。当counter等于0时,假设当前元素就是答案。并把count置1. 
	 * 之后,如果遇到一个不同的元素,--count. 继续下一个元素。 相同的++count,继续下一个元素。
	 * 当再count == 0的时候,则假设当前的元素是答案。继续这个过程。
	 */
	public int majorityElement(int[] nums) {
		int ans = 0, counter = 0;
		for (int num : nums) {
			if (counter == 0) {
				ans = num;
				counter = 1;
			} else if (num == ans) {
				++counter;
			} else if (num != ans) {
				--counter;
			}
		}
		return ans;
	}
	
	// Way 4 Bit Manipulate
	/**
	 * 个人觉得这个方法较为牵强,这是从底层的角度来思考。用32位的数组记录每一位在
	 * 目标数组的数据中为1的次数。如果某个数出现多个n/2次,那么其每一位也会出现多于n/2次.
	 */
	public int majorityElement(int[] nums) {
		int[] records = new int[32];
		for (int num : nums) {
			for (int i = 0; i < 32; ++i) {
				records[i] += ((num >>> (31 - i)) & 1); 
			}
		}
		int res = 0;
		int n = nums.length;
		for (int j = 0; j < 32; ++j) {
			if (records[j] > (int)(n/2)) {
				res |= (1 << 31 - j);
			}
		}
		return res;
	}
}




Largest power of 2 (most significant bit in binary form), which is less than or equal to the given number N.


long largest_power(long N) {
    //changing all right side bits to 1.
    N = N | (N>>1);
    N = N | (N>>2);
    N = N | (N>>4);
    N = N | (N>>8);
    N = N | (N>>16);
    return (N+1)>>1;
}


注意逻辑是:右移一位后并或之后,所有原来是1的右边也是1. 所以有1都是连续2个。记住,只有移动到16方能保险。(无论是int还是long). 


2)   将right most bit(为1)置为0: A & (A - 1)。经常用以提取1的个数。


例题:191. Number of 1Bits


/**
For example, the 32-bit integer ’11' has binary representation 00000000000000000000000000001011, so the function should return 3.
*/
public class Solution {
    // you need to treat n as an unsigned value
    public int hammingWeight(int n) {
        int tmp = n;
        int count = 0;
        while (tmp != 0) {
            tmp = tmp & (tmp - 1);
            ++count;
        }
        return count;
    }
}


例题:338 Counting Bits


/**
Given a non negative integer number num. For every numbers i in the range 0 ≤ i ≤ num calculate the number of 1's in their binary representation and return them as an array.

Example:
For num = 5 you should return [0,1,1,2,1,2].

It is very easy to come up with a solution with run time O(n*sizeof(integer)). But can you do it in linear time O(n) /possibly in a single pass?
Space complexity should be O(n).
Can you do it like a boss? Do it without using any builtin function like __builtin_popcount in c++ or in any other language.
*/

public class Solution {
    public int[] countBits(int num) {
        int[] res = new int[num + 1];
        for (int i = 0; i <= num; ++i) {
        	int tmp = i;
        	int count = 0;
        	while (tmp != 0) {
        		tmp = tmp & (tmp - 1);
        		++count;
        	}
        	res[i] = count;
        }
        return res;
    }
}



例题: 231 Power of Two


/**
Given an integer, write a function to determine if it is a power of two.
*/
class Solution {
    public boolean isPowerOfTwo(int n) {
        if (n > 0) {
            int tmp = n;
            tmp = tmp & (tmp - 1);
            if (tmp == 0) {
                return true;
            }
        }
        return false;
    }
}

例子:342 Power of Four


class Solution {
    public boolean isPowerOfFour(int num) {
        if (num > 0) {
            if ((num & (num - 1)) == 0 && (num & 0x55555555) != 0) {
                return true;
            } 
        }
        return false;
    }
}

3) 用^来获取pairs里面落单的或者做加法


落单例子1:

136. Single Number


/**
Given an array of integers, every element appears twice except for one. Find that single one.
*/
class Solution {
    public int singleNumber(int[] nums) {
        int res = 0;
        for (int i : nums) {
            res ^= i;
        }
        return res;
    }
}

落单例子2:


/**
Given an array containing n distinct numbers taken from 0, 1, 2, ..., n, find the one that is missing from the array.
For example,
Given nums = [0, 1, 3] return 2.
思路:本来完整的array应该是从0到n有n+1个元素。那么只需要将array的每个元素与0, n异或,剩下的那个结果就是缺的。
*/
class Solution {
    public int missingNumber(int[] nums) {
        int n = nums.length;
        int res = 0;
        for (int i = 0; i < n; ++i) {
            res ^= i ^ nums[i];
        }
        res ^= n;
        return res;
    }
}


加法:371. Sum of Two Integers


/**
Calculate the sum of two integers a and b, but you are not allowed to use the operator + and -.

Example:
Given a = 1 and b = 2, return 3.

a作为直接加的数,b作为进位,继续递归。
*/
class Solution {
    public int getSum(int a, int b) {
        if (b == 0) {
            return a;
        }
        int newA = a ^ b;
        int newB = (a & b) << 1;
        return getSum(newA, newB);
    }
}

4) & 用来select a certain bit. 


例题: 201. Bitwise AND of Numbers Range


这道题需要知道一个小知识点。一个连续区间的所有数相位与,答案是在于两个阈值是否存在某个1之后(向左看,包括当前的1)相同,如果不存在,则得到0. 因为从最低值到最高值,变化的所有值会把所有的位置相与置0. 


例如[24, 40],24的binary是011000而40的binary是101000,得到的结果应该是0. 


再例如[24, 31], 31的binary是011111,共同位有11000,得到的结果是24. 


代码:


/**
Given a range [m, n] where 0 <= m <= n <= 2147483647, return the bitwise AND of all numbers in this range, inclusive.

For example, given the range [5, 7], you should return 4.
*/

class Solution {
    public int rangeBitwiseAnd(int m, int n) {
        int count = 0;
    	while (m != n) {
    		//注意,有可能会是0. 
        	m = m >> 1;
        	n = n >> 1;
        	++count;
        }
    	return m << count;
    }
}


5)用bit来形成的mask来表示各种可能的组合,节省空间复杂度。


例题:187. Repeated DNA Sequences


这一题可以直接用O(n)每个元素占8位(一个char)的空间来做:


class Solution {
    public List<String> findRepeatedDnaSequences(String s) {
        if (s == null || s.length() == 0 || s.length() <= 10) {
        	return null;
        }
        List<String> ans = new ArrayList<>();
        Set<String> first = new HashSet<>();
        int n = s.length();
        for (int i = 0; i < n - 9; ++i) {
        	String tmp = s.substring(i, i + 9);
        	// 这里需要注意,有三种情况
        	// 1) 从未出现过; 2)出现过一次; 3)出现过两次或以上。
        	if (first.contains(tmp) == false
        			&& ans.contains(tmp) == false) {
        		first.add(tmp);
        	} else if (first.contains(tmp)) {
        		first.remove(tmp);
        		ans.add(tmp);
        	} else if (ans.contains(tmp)) {
        		continue;
        	}
        }
        return ans;
    }
}

也可以用mask的方法来做,这里的空间负责度也是O(n),不过每个元素用4位,毕竟有两个set:


class Solution {
    public List<String> findRepeatedDnaSequences(String s) {
    	List<String> ans = new ArrayList<>();
    	if (s == null || s.length() == 0 || s.length() <= 10) {
        	return ans;
        }
    	// 以下的dictionary准备工作可以直接用switch来做,不过用一个数组O(1)
    	// 的获取优势,比分支的时间要短。分支需要开销。(jump table, 需要cmp, je, jg..)
        char[] dict = new char[26];
        dict[0] = 0;
        dict['C' - 'A'] = 1;
        dict['G' - 'A'] = 2;
        dict['T' - 'A'] = 3;
        Set<Integer> first = new HashSet<>();
        Set<Integer> second = new HashSet<>();
        int n = s.length();
        for (int i = 0; i < n - 9; ++i) {
        	int tmp = 0;
        	for (int j = i; j <= i + 9; ++j) {
        		tmp = tmp << 2;
        		tmp |= dict[s.charAt(j) - 'A'];
        	}
        	if (first.contains(tmp) == false
        			&& second.contains(tmp) == false) {
        		first.add(tmp);
        	} else if (first.contains(tmp)) {
        		first.remove(tmp);
        		second.add(tmp);
        		ans.add(s.substring(i, i + 10));//注意前闭后开
        	} else if (second.contains(tmp)) {
        		continue;
        	}
        }
        return ans;
    }
}

6) 两题较为特别的Single Number


137. Single Number II


/**
Given an array of integers, every element appears three times except for one, which appears exactly once. Find that single one.
这道题的这种做法适用于只有一个数出现的次数和别的数不一样。也是在bit的层面上理解。如果某个bit上出现的1的次数不是3的倍数,
则在那个bit上要赋值1. 同理,如果是其它出现5次,则是5的倍数。
*/
class Solution {
    public int singleNumber(int[] nums) {
        int res = 0;
        int sum = 0;
        int n = nums.length;
        for (int i = 0; i < 32; ++i) {
            sum = 0;
            for (int j = 0; j < n; ++j) {
                if (((nums[j] >> (31 - i)) & 1) != 0) {
                    sum += 1;
                }
            }
            if (sum % 3 != 0) {
                    res |= (1 << (31 - i));
            }
        }
        return res;
    }
}


260. Single Number III


特别加粗一个很重要的trick,获得right most bit的mask的方法: tmp &= (-tmp). 


/**
Given an array of numbers nums, in which exactly two elements appear only once and all the other elements appear exactly twice. Find the two elements that appear only once.

For example:

Given nums = [1, 2, 1, 3, 2, 5], return [3, 5].

Note:
The order of the result is not important. So in the above example, [5, 3] is also correct.
Your algorithm should run in linear runtime complexity. Could you implement it using only constant space complexity?

这一题的single number是其它都为2次。出现偶数次应该想到异或。那么其它的元素异或为0. 最后异或得到的结果是唯一的两个不一样数异或结果。
那么对应是1的位置是一个数有,一个数没有。所以,以这个1作为分类的标准,将原有的组分为两类分别异或,即可得到答案想要的两个数。
这里需要提醒一个技巧,如何获得最右边的那位1的mask?tmp & (-tmp)即可。除了最右边的1,其余位都是0. 例如2是0000...10,作为负数是:111111111..10,因为111111..11是-1. 
*/

class Solution {
    public int[] singleNumber(int[] nums) {
        // first pass
        int firstXOR = 0;
        for (int num : nums) {
            firstXOR ^= num;
        }
        // get the right most bit
        firstXOR &= (-firstXOR);
        int[] res = new int[2];
        // second pass
        for (int num : nums) {
            if ((num & firstXOR) != 0) {
                res[0] ^= num;
            } else {
                res[1] ^= num;
            }
        }
        return res;
    }
}














评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值