方案一
我们可以利用为位与操作,依次判定各个位上是否为1。
public int hammingWeight(int n) {//依次检测各个位
int count=0;
int location=1,value=1;
while(location<=32){
if((n&value)!=0){
count++;
}
location++;
value<<=1;
}
return count;
}
陷阱
public static int hammingWeight(int n) {//错误代码,不断检测最低位
int count=0;
int t=n;
while(t!=0){
count+=t&1;
t>>=1;
}
return count;
}
如果我们通过不断检测最低位来统计1的个数,与此同时整数不断右移。对于正整数我们似乎可以得到正确的结果,那是因为右移后高位补0。
但是对于负整数,右移后高位补1,那么最终整数将会变成0xFFFFFFFF而进入死循环。
修正
public static int hammingWeight(int n) {//不断检测最高位
int count=0;
int t=n,max=1<<31;
while(t!=0){
count+=(t&max)==0?0:1;
t=t<<1;
}
return count;
}
对于左移,由于正数和负数低位都是补0。因此我们可以不断检测最高位来统计1的个数。
优化
public static int hammingWeight(int n) {
int count=0;
int t=n;
while(t!=0){
t=t&(t-1);
count++;
}
return count;
}
上面的算法复杂度为O(k),k为二进制的位数(这里为32)。而该方法将复杂度降低为O(m),m为二进制中1的个数,显然m<=k。
原理:例如n=1100 1100,n-1=1100 1011,n&(n-1)=11001000,不难发现,n&(n-1)可以消除最低位的那个1。
分析
方案一:对于2的幂,其二进制表示中,1的个数必定只有一个。此外,还需要注意2的幂必为正数。
public class Solution {
public boolean isPowerOfTwo(int n) {
return hammingWeight(n)==1&&n>0;
}
public int hammingWeight(int n) {
int count=0;
int t=n;
while(t!=0){
t=t&(t-1);
count++;
}
return count;
}
}
方案二:我们知道n&(n-1)可以消除掉最低位的1,那么2的幂二进制位中只有一个1,因此我们只需判断n&(n-1)==0即可。
public class Solution {
public boolean isPowerOfTwo(int n) {
return (n&(n-1))==0&&n>0;
}
}
方案三:我们知道正整数中最大的数是1<<30,我们直接判定1<<30能不能被n整除即可。
public class Solution {
public boolean isPowerOfTwo(int n) {
return n>0&&((1<<30)%n)==0;
}
}
方案四:我们知道整数范围内2的幂就那么几个,我们直接用switch语句进行比较就可以了。略。
方案五:将n不断除以2,并且没有余数,即可判定其是否为2的幂。显然除法效率没有位移运算高效,我们可以利用右移代替除2。
方案一:首先我们可以判定其是否为2的幂,然后再判定1的位置。
public boolean isPowerOfFour(int num) {
if(!isPowerOfTwo(num)){
return false;
}
int location=1,value=1,n=num;
while(location<=32){
if((n&value)!=0){
break;
}
location++;
value<<=1;
}
return location%2==0;
}
public boolean isPowerOfTwo(int n) {
return (n&(n-1))==0&&n>0;
}
如果不用循环或者递归,我们该如何处理呢?
方案二:对于1在哪个位置上的判断我们可以做如下优化。
public class Solution {
public boolean isPowerOfFour(int num) {
return num > 0 && (num&(num-1)) == 0 && (num & 0x55555555) != 0;
//5的二进制表示为0101,因此0x55555555覆盖了所有我们期望1出现的位置
}
}
方案三:对于区别4的幂和2的幂,还可以如下判断。
public class Solution {
public boolean isPowerOfFour(int num) {
return num > 0 && (num&(num-1)) == 0 && (num-1)%3==0;
//n-1,的低位是连续的1,1的个数为偶数,而3的二进制位11,1111=1100+0011也必定是3的整数被,因此偶数个1就是3的整数倍。
}
}
方案四:整数范围内4的幂就那么几个,我们直接利用switch语句进行比较就可以了。
分析
方案一:整数中最大的3的幂为3^19(1162261467),如果3^19能被n整除,n就是3的幂。
public class Solution {
public boolean isPowerOfThree(int n) {
// 1162261467 is 3^19, 3^20 is bigger than int
return ( n>0 && 1162261467%n==0);
}
}
方案二:用switch语句。
方案三:可以不断除3,以判断余数。
对于a=0100 1100,b=0111 0100。
a^b= 0011 1000,代表各个位对应相加后该位的结果,不考虑进位。
a&b=0100 0100,表示各个位对应相加后的进位情况,我们需要将进位累加到高一位上去,(a&b)<<1=1000 1000。.
因此a+b=a^b+(a&b)<<1,1100 0000。加法的实质,也就是第一个操作数的特定位上需要进位,而第二个操作数指明是哪些位。
这样递归处理,最终每个位都不需要进位的时候,就得到了我们计算的结果。
public int getSum(int a, int b) {
if(b == 0){//没有进位的时候完成运算
return a;
}
int sum,carry;
sum = a^b;//第一步完成各个位上的加发运算
carry = (a&b)<<1;//第二步完成进位运算(左移运算)
return getSum(sum,carry);//
}
分析
我们可以用等长的二进制位000分别表示对应元素是否选取。000表示三个元素都不选取,结果为[],101表示选取第一个和第三个元素,结果为[1,3]。
我们将二进制位从000一直加到111,就得到了所有的子集合。
public class Solution {
int[] choose=null;
private boolean isEnd(){//是否到结尾了,全选
for(int i:choose){
if(i==0) return false;
}
return true;
}
private void next(){//模拟二进制加1
int add=1;
for(int i=0;i> subsets(int[] nums) {
List> res=new ArrayList>();
if(nums.length==0){
res.add(new ArrayList());
return res;
}
choose=new int[nums.length];
while(true){
List r=new ArrayList();
for(int i=0;i
对于组合问题,我们还可以利用回溯或者分期摊还的方法进行处理,参考:
https://discuss.leetcode.com/topic/19110/c-recursive-iterative-bit-manipulation-solutions-with-explanations
http://blog.csdn.net/sunxianghuang/article/details/51887505
如果a=b,那个a^b=0。因此,我们将所有元素进行异或就可以得到结果。
public class Solution {
public int singleNumber(int[] nums) {
int res=0;
for(int i:nums){
res^=i;
}
return res;
}
}
此外,我们还可以利用哈希表来进行频率统计,但是效率没有位运算高。
遍历元素,我们可以分别统计每个二进制位上出现1的次数。对于三个相同的元素,他们对特定位上1的个数的贡献肯定是3的倍数,因此我们将所有二进制位上1的次数%3,最后每个二进制位上的1就是那个单独元素贡献的,因此我们就可以计算出最终的结果了。
public class Solution {
public int singleNumber(int[] nums) {
int length = nums.length;
int result = 0;
for(int i = 0; i<32; i++){
int count = 0;//统计所有元素,该位上1出现的次数
int mask = 1<< i;
for(int j=0; j
扩展
问题转换为:给定数组中,只有一个元素出现 P 次,其余元素都出现K 次,且p>=1&&p%k!=0,找出这个出现 P 次的唯一元素。
方法论
依照之前的思想,我们对每一个二进制位统计出现1的次数,然后对k求余。最终次数不为零的位,就是唯一元素二进制为1的位。
网上还存在一种全部使用位操作实现的算法,但很少给出具体解析,这里进行一下整理。
对于二进制位中1出现次数的统计,我们可以采用位操作实现,这样更加高效。分析如下:
1、如何用二进制来表示1出现的次数
对于一般元素,出现次数为 K ,要想表示整数K, 我们需要 m 个二进制位来表示( 2^m >= k)。由于我们需要统计整数的32个位,因此我们需要32*m个二进制位来统计1的出现次数。如下图所示,例如用X1{5}X2{5}....Xm{5}(低位->高位)组成的二进制数来表示第5个二进制位上1出现的次数。其中,Xi(1<=i<=m)是一个32位整数,Xi{5}表示整数Xi二进制表示中的第5个二进制位。
2、如何用二进制位来统计1的次数
每处理一个元素 E ,特定位上要么是1要么是0。对于第5个二进制位,如果二进制数X1{5}X2{5}....Xm{5}的最高位Xm{5}想要增加(必须通过低位进位来实现,也就是说低位必须全部都为1且需要加上1),其需要满足的条件是:E 的第五个二进制位为1,记做 E{5}=1,且X1{5}==1&&X2{5}==1&&....&& Xm-1{5}==1。同理,Xi{5}想要增加的条件是,X1{5}==1&&X2{5}==1&&....&& Xi-1{5}==1。
因此,对于二进制数X1{5}X2{5}....Xm{5},Xi{5}增加的条件满足时我们需要对其+1,条件不满足时什么都不做。
回想一下,如果第二个操作数为0则第一个操作数不会改变的运算有哪些?你会想到x=x|0和x=x^0。
这里我们利用异或运算(^),例如Xi{5}=Xi{5}^(X1{5}==1&&X2{5}==1&&....&& Xi-1{5}==1),如果条件不成立,Xi{5}保持不变,如果条件成立Xi{5}就是加1,原来是1就变成0,原来是0就变成1。这样,我们就实现了利用二进制进行1的次数统计。
3、统计次数的重置
例如,我们的K 为5,5需要3位二进制表示,5的二进制表示为"101",那么我们的统计次数达到"101"之后应该重新置为零,否则,继续往上统计达到“111”之后会产生溢出,统计就会出错。也就是说我们需要找到一个特定的条件,当这个条件满足时进行置零,条件不满足时什么都不会发生,这里我们依旧使用与运算(&)和非运算(~)。例如二进制数X1{5}X2{5}....Xm{5},代表第5个二进制位上1的次数,假设K为5,那么m=3,如果想要对其置零,需要满足的条件就是X1{5}==1&&X2{5}==0&&X3{5}==1。
X1{5}X2{5}....Xm{5}=X1{5}X2{5}....Xm{5} &(~ (X1{5}==1&&X2{5}==0&&X3{5}==1) )。这样我们实现了次数的重置。
分析到这里,我们现在跳出对单个二进制位的分析。而对整数中32个二进制位统一考虑。
因此对于次数统计,Xm=Xm^(Xm-1&..&X2&X1),这里利用与运算(&)代替条件判定。
对于次数的重置,Xm=Xm&condition,condition满足这样的性质:当且仅当1的次数统计为K时其结果为0,其余情况都为1。
condition= ~(x1' & x2' & ... xm'),where xj' = xj if kj = 1 and xj' = ~xj if kj = 0 (j = 1 to m),其中kj 就是K的二进制表示中第j个二进制位的值,例如K为5("101"),那么condition=~(X1&~X2&X3)。
对于此题,K=3(二进制表示"11")、P=1,因此m=2,condition=~(X1&X2)。得到如下算法:
public class Solution {
public int singleNumber(int[] nums) {
int x1 = 0;
int x2 = 0;
int condition = 0;
for (int i : nums) {
x2 ^= x1 & i;
x1 ^= i;
condition = ~(x1 & x2);
x2 &= condition;
x1 &= condition;
}
return x1;
}
}
这里的结果直接就是x1,因为p=1,二进制表示位“1”,x1中每个二进制位上的值就是最终结果对应二进制位上的值。如果p=2呢,2的二进制表示位“10”,因此x2中每个二进制位上的值就是最终结果对应二进制位上的值,而x1最终为零。
如果K=9、P=7,那么m=4,7的二进制位“0111”(高位到低位),因此x1、x2、x3中二进制位上的值都与结果对应二进制位上的值相同,因此都能作为最终结果,只有x4最终为零。
参考
https://discuss.leetcode.com/topic/11877/detailed-explanation-and-generalization-of-the-bitwise-operation-method-for-single-numbers
分析
将所有元素异或后的结果即为5^3,5和3的二进制表示分别为0101和0011,异或的结果为0110,我们根据元素的二进制表示中从低位开始第二个(2^1)二进制位是否为1将数组划分为两部分[1,1,3],[2,3,2],这样问题就退化为问题137啦。
public class Solution {
public int[] singleNumber(int[] nums) {
int[] res=new int[2];
int two=singleNumberSimple(nums);
int location=0;
while(location<=31){
if(((1< first=new ArrayList();
ArrayList second=new ArrayList();
for(int i:nums){
if(((1< nums) {
int res=0;
for(int i:nums){
res^=i;
}
return res;
}
public int singleNumberSimple(int[] nums) {
int res=0;
for(int i:nums){
res^=i;
}
return res;
}
}
分析
对于长度为n的数组,缺失数的范围为0~n。缺失,也就是说我们需要进行存在性判断,因此我们可以使用哈希表,有因为需要判定的数据在限定范围内,因此我们可以使用数组来实现哈希表,此外也可以使用HashMap记录0~n-1的存在性。
public class Solution {
public int missingNumber(int[] nums) {
int n=nums.length;
if(n==0) return 0;
int[] mark=new int[n];
for(int i:nums){
if(i>=0&&i
此种解法的空间复杂度为O(n),时间复杂度为O(n)。那么如何将空间复杂度降低为O(1)呢?
我们可以利用数组本身来做存在性判定,如果存在值 i 我们保证a[i]存放的就是值 i ,然后依次检查a[i]上存放的是不是 i 即可。
public class Solution {
public int missingNumber(int[] nums) {
int n=nums.length;
if(n==0) return 0;
for(int i=0;i
分析
对于单词字符串的字符出现情况我们可以使用一个哈希表来记录,由于范围限定在‘a’~'z',我们可以直接使用26位的数组,由于只需标记字符是否出现(状态只有0和1),因此我们只需要用一个位来表示该字符是否存在,因此我们只需要一个32位的整型变量,其32个二进制位就可以标记字符出现情况,并且我们在匹配两个字符串出现字符是否有重复时,我们只需要使用位与(&)即可。
public int maxProduct(String[] words) {
int res=0,n=words.length;
if(n==0) return res;
int[] mark=new int[n];
for(int i=0;i
分析
n的二进制表示中1个数等于n>>1的二进制表示中1的个数+n的二进制表示中最低位1的个数(n&1)。因此,我们就避免了对每一个正数进行计算二进制中1的个数。
public class Solution {
public int[] countBits(int num) {
int[] res=new int[num+1];
for(int i=1;i<=num;i++){
res[i]+=(res[i>>1]+(i&1));
}
return res;
}
}
变形
如果我们不是计算每个整数的二进制表示中1的个数。而是统计0至n所有整数的二进制表示中1的个数呢?或者更精确的讲,统计
每个二进制位上1的出现次数呢?
对于所有整数二进制表示中1的个数,显然可以按照上面的方法,先依次计算再进行求和。时间复杂度和空间复杂度都是O(n)。
而对于每个二进制位上1的出现次数,我们可以迭代所有整数,利用位与(&)判定各个位上的值,并进行统计,时间复杂度O(n)。
接下来介绍一种更加高效的算法,时间复杂度O(1)。
首先我们观察0000->1111的变化情况,低位到高位
0000
1000
0100
1100
0010
1010
0110
1110
0001
1001
0101
1101
0011
1011
0111
1111
发现第一个二进制位变化周期为1,第二个二进制位变化周期为2,第三个二进制位变化周期为4,第四个二进制位变化周期为8。
因为0~1的变化是呈现周期性的,故0000->1111,各个位上出现1的情况都是一半。总共2^4=16个数,每个位1出现次数为(2^4)/2。
现在我们来分析更一般的情况:0000 0000->0010 0101。我们将这样的变化过程进行分解为:
第一步:0000 0000->1111 1110
第二步:0000 0001->1111 1001
第三步:0000 0101->1100 0101
第四步:0010 0101
第一步:0000 0000->1111 1110总共2^7个数,前7个二进制位,每个二进制位上1的个数(2^7)/2。
第二步:0000 0001->1111 1001总共2^5个数,前5个二进制位,每个二进制位上1的个数(2^5)/2,第8个二进制位永远是1。
第三步:0000 0101->1100 0101总共2^2个数,前2个二进制位,每个二进制位上1的个数(2^2)/2,第6、8个二进制位永远是1。
第四步:1个数,第3、6、8个二进制位是1。
因此我们不难得出如下算法:
public int[] countBits(int num) {
int[] res=new int[32];
ArrayList indexs=new ArrayList();//1出现的位置
int location=30;//因为32位的int,正数高位为0
while(location>=0){
if((num&(1<>1;
}
indexs.add(location);
}
location--;
}
for(int i:indexs){
res[i]+=1;
}
return res;
}
扩展
将问题扩展到十进制中。对于0~n的整数,分别统计0~9出现的次数?分别统计每个位上0~9出现的次数呢?