n-=n&-n,在这里是什么意思啊

题目:
输入一个 32 位整数,输出该数二进制表示中 1 的个数。
样例1:
输入:9
输出:2
解释:9的二进制表示是1001,一共有2个1。
样例2:
输入:-2
输出:31
解释:-2在计算机里会被表示成11111111111111111111111111111110,
一共有31个1。


class Solution {
public:
    int NumberOf1(int n) {
        int res=0;
        
        while(n) n-=n&-n,res++;
        
        return res;
    }
};

这个是微软的一个题,考的答案就是问一个数有几个1组成
原代码这样


int func(int x) {
int count = 0;
while (x) {
count++;
x = x & (x - 1);
}
return count;
}

n-=n&-n
n=n-n&-n
例如9(1001)
n=9-(9 & -9)
按位与 同1为1 其他为0
9 & -9
= 1001 & 111...111110111此时你是几位类型就有几位 比如long为64位就是60个1然后0111 int32位
= 0001
继续循环此时n=9-1=8
8 & -8
=1000 & 111...1111000
=1000
此时n=8-8=0退出循环
相当于从后往前把1去掉 最后只剩0 退出循环 计算循环次数了。

n -= (n & -n)
这样就清晰一点了,首先做位运算,然后做赋值运算,主要就是优先级的判断

一步步来
&是位与
n&(-n)表示一个数和自身相反数的补码相与, 算出来的结果一定是n的原码表示中最后一位1,
n -= n&(-n)就是n减去最右边一个二进制位上1对应的十进制值, 也就是去掉了最右边一位1
每进行一次while, 就表示n的二进制表示有一个1

这题除了&与能做, ^异或也能做

n-=n&-n
&代表与运算。2个都为1,则为真
n&-n是做二进制运算
如n=5;
5的二进制为00000101
-5的二进制为10000101
进行与运算
只有第三位和第一位结果为1.
所以结果为2

n&-n可以获得最低的非0位,例如n=110010,n&-n会获得10,n=n-(n&-n)=110000,此时n&-n=10000,n=n-(n&-n)=110000=100000,此时n&-n=100000,n=n-(n&-n)=0
每次获得最低的非0位,然后减去它,直到n最后变为0,过程循环几次,说明原来的n里有几个1

while(n)相当于while(n!=0)

n-=n&-n相当于n = n - (n&-n)

至于n&-n,这里实际运算是使用n和-n的补码进行与运算的
例如:
3 = 0000 0000 0000 0000 0000 0000 0000 0011
-3 = 0000 0000 0000 0000 0000 0000 1000 0011
而补码规则:
1、正数的补码就是其本身
2、负数的补码的规则,符号不变, 其余位取反, 最后是加1

0000 0000 0000 0000 0000 0000 0000 0011 //3的补码
& 
1111 1111 1111 1111 1111 1111 1111 1101 //-3的补码

结果为0000 0001,也就是1

再比如
-2 = 1000 0000 0000 0000 0000 0000 0000 0010
2 = 0000 0000 0000 0000 0000 0000 0000 0010

1111 1111 1111 1111 1111 1111 1111 1110 //-2的补码
& 
0000 0000 0000 0000 0000 0000 0000 0010 //2的补码

这里结果是2,然后用-2-2 = -4,依次循环下去
每次获得最低的非0位,然后减去它,直到n最后变为0,过程循环几次,说明原来的n里有几个1

补充:
使用逗号运算符是为了把几个表达式放在一起。

整个逗号表达式的值为系列中最后一个表达式的值。

从本质上讲,逗号的作用是将一系列运算按顺序执行。

表达式1, 表达式2
求解过程是:先求解表达式 1,再求解表达式 2。整个逗号表达式的值是表达式 2 的值。

最右边的那个表达式的值将作为整个逗号表达式的值,其他表达式的值会被丢弃。

例如:

var = (count=19, incr=10, count+1);

在这里,首先把 count 赋值为 19,把 incr 赋值为 10,然后把 count 加 1,最后,把最右边表达式 count+1 的计算结果 20 赋给 var。上面表达式中的括号是必需的,因为逗号运算符的优先级低于赋值操作符。