一、题目描述
二、解题思路
- 解法1:逐步查看该数字的最低位,看是否为 1 1 1,然后把数字左移 1 1 1位。时间复杂度为 O ( 32 n ) O(32n) O(32n)
- 解法2:将数字不断与 ( n − 1 ) (n-1) (n−1)相与,每次计算完成后,只要数字不为 0 0 0,就说明存在 1 1 1个 1 1 1
三、解题代码
解法1
class Solution {
public:
int hammingWeight(uint32_t n) {
int count = 0;
while(n){
if(n & 1)
count++;
n = n >> 1;
}
return count;
}
};
解法2
class Solution {
public:
int hammingWeight(uint32_t n) {
int count = 0;
while(n){
count++;
n &= (n - 1);
}
return count;
}
};
三、测试程序
/*******************************************************************
Copyright(c) 2016, Harry He
All rights reserved.
Distributed under the BSD license.
(See accompanying file LICENSE.txt at
https://github.com/zhedahht/CodingInterviewChinese2/blob/master/LICENSE.txt)
*******************************************************************/
//==================================================================
// 《剑指Offer——名企面试官精讲典型编程题》代码
// 作者:何海涛
//==================================================================
// 面试题15:二进制中1的个数
// 题目:请实现一个函数,输入一个整数,输出该数二进制表示中1的个数。例如
// 把9表示成二进制是1001,有2位是1。因此如果输入9,该函数输出2。
#include <cstdio>
/*
int NumberOf1_Solution1(int n)
{
int count = 0;
unsigned int flag = 1;
while (flag)
{
if (n & flag)
count++;
flag = flag << 1;
}
return count;
}
int NumberOf1_Solution2(int n)
{
int count = 0;
while (n)
{
++count;
n = (n - 1) & n;
}
return count;
}
*/
int NumberOf1_Solution1(int n)
{
int count = 0;
while (n)
{
count++;
n &= (n - 1);
}
return count;
}
int NumberOf1_Solution2(int n)
{
int count = 0;
while (n)
{
if (n & 1)
count++;
n = n >> 1;
}
return count;
}
// ====================测试代码====================
void Test(int number, unsigned int expected)
{
int actual = NumberOf1_Solution1(number);
if (actual == expected)
printf("Solution1: Test for %p passed.\n", number);
else
printf("Solution1: Test for %p failed.\n", number);
actual = NumberOf1_Solution2(number);
if (actual == expected)
printf("Solution2: Test for %p passed.\n", number);
else
printf("Solution2: Test for %p failed.\n", number);
printf("\n");
}
int main(int argc, char *argv[])
{
// 输入0,期待的输出是0
Test(0, 0);
// 输入1,期待的输出是1
Test(1, 1);
// 输入10,期待的输出是2
Test(10, 2);
// 输入0x7FFFFFFF,期待的输出是31
Test(0x7FFFFFFF, 31);
// 输入0xFFFFFFFF(负数),期待的输出是32
Test(0xFFFFFFFF, 32);
// 输入0x80000000(负数),期待的输出是1
Test(0x80000000, 1);
return 0;
}
四、运行结果
Solution1: Test for 0000000000000000 passed.
Solution2: Test for 0000000000000000 passed.
Solution1: Test for 0000000000000001 passed.
Solution2: Test for 0000000000000001 passed.
Solution1: Test for 000000000000000a passed.
Solution2: Test for 000000000000000a passed.
Solution1: Test for 000000007fffffff passed.
Solution2: Test for 000000007fffffff passed.
Solution1: Test for 00000000ffffffff passed.
Solution2: Test for 00000000ffffffff passed.
Solution1: Test for 0000000080000000 passed.
Solution2: Test for 0000000080000000 passed.
五、复盘
我发现LeetCode上的都是CodeInterview上的弱化版本,实际上CodeInterview里要考虑数据为负数的情况
那么我的第二种解法就不好使了,比如数据为0x80000000
,这可是个负数
因为int
型数据正数为原码,负数为补码。
最高位是符号位,正数最高位是0,负数最高位为1
那么对于负数1111 1111 1111 1111 1111 1111 1111 1111
:其值为
−
1
-1
−1
那么对于负数1000 0000 0000 0000 0000 0000 0000 0001
:其值为
−
2
,
147
,
483
,
647
-2,147,483,647
−2,147,483,647
但是别忘了
+
0
=
=
−
0
+0 == -0
+0==−0,所以多出了一个数据,那么就使用
−
0
-0
−0即1000 0000 0000 0000 0000 0000 0000 0000
即0x8000000
来表示更大的数字,即
−
2
,
147
,
483
,
648
-2,147,483,648
−2,147,483,648
那么使用解法1来计算0x8000000
,将会卡在
−
1
-1
−1然后进入死循环
为什么呢,因为负数在内存中以补码(补码:符号位不变,其余位按照原码取反加1)形式保存,右移时保证符号位不变,其余位向右移动到X位,在移动的过程中,高位补1
那么对于1000 0000 0000 0000 0000 0000 0000 0000
,势必最后变成1111 1111 1111 1111 1111 1111 1111 1111
,这个数字是-1
,于是再怎么位移都是-1
,死循环,所以跳出循环的条件还要加上是不是等于-1
综上,代码优化为:
int NumberOf1_Solution2(int n)
{
int count = 0;
if (n < 0)
{
if (n == -1)
return 32;
count++;
}
while (n && n != -1)
{
if (n & 1)
count++;
n = n >> 1;
}
return count;
}