这是我参与8月更文挑战的第23天,活动详情查看:8月更文挑战
一、前言
大家好,本文章属于《剑指 Offer 每日一题》中的系列文章中的第 12 篇。
在该系列文章中我将通过刷题练手的方式来回顾一下数据结构与算法基础,同时也会通过博客的形式来分享自己的刷题历程。如果你刚好也有刷算法题的打算,可以互相鼓励学习。我的算法基础薄弱,希望通过这两三个月内的时间弥补这块的漏洞。本次使用的刷题语言为 Java ,预计后期刷第二遍的时候,会采用 Python 来完成。
- 刷题平台: leetcode 的剑指 Offer 专题
- 码云仓库地址
二、题目
编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 '1' 的个数(也被称为 汉明重量).)。
提示:
- 请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
- 在 Java 中,编译器使用 二进制补码 记法来表示有符号整数。因此,在上面的 示例 3 中,输入表示有符号整数 -3。
示例1:
输入:n = 11 (控制台输入 00000000000000000000000000001011)
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。
示例2:
输入:n = 128 (控制台输入 00000000000000000000000010000000)
输出:1
解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。
示例3:
输入:n = 4294967293 (控制台输入 11111111111111111111111111111101,部分语言中 n = -3)
输出:31
解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 '1'。
三、解题
3.1 思路1:逐位判断
根据 & 运算,有 整数 n,它有以下的定义:
- 1、如果 n & 1 ==0, 则 n 的二进制最后一位为 0
- 2、如果 n & 1 ==1, 则 n 的二进制的最后一位为 1.
例如,执行下面的代码 值为 1和 0
System.out.println(3&1); //1
System.out.println(4&1); //0
那我们就可以逐位来计算,整数总共 32 位
<< : 左移运算符,num << 1,相当于num乘以2
>> : 右移运算符,num >> 1,相当于num除以2
>>> : 无符号右移,忽略符号位,空位都以0补齐
3.1.1 代码
public static int Solution(int n){
int res =0;
for (int i = 0; i < 32; i++) {
System.out.println(3 &1);
res +=(n>>i&1); // 右移
}
return res;
}
3.1.2 执行效果
3.1.3 复杂度
- 时间复杂度:O(k)O(k),k 是 int 的位数,固定为 3232 位
- 空间复杂度:O(1)O(1)
3.2 思路2:右移统计
这是对思路1 的一个优化,思路1 中的高位数也要进行循环检查,其实是可以避免的,直接右移 n 即可。
3.2.1 代码
public static int Solution2(int n){
int res =0;
while (n!=0){
res+=(n>>=1&1);
n>>>=1;
}
return res;
}
3.2.2 执行效果
3.3 思路3:惊喜的解法,巧用 n&(n-1)
把一个整数减去 1,都是把最右边的 1 变为0,如果它的右边还有 0,则所有的 0都变为1,而它的左边所有位保持不变,总结 就是 用它可以消去 数字 n 的最右边的1.
3.3.1 代码
public class Solution3 {
public int hammingWeight(int n) {
int res = 0;
while(n != 0) {
res++;
n &= n - 1;
}
return res;
}
}
3.3.2 执行效果
3.3.3 复杂度
- 时间复杂度 O(M)
- 空间复杂度 O(1)
3.4 思路4 :API
public static int Solution4(int n){
return Integer.bitCount(n);
}
四、总结
本题考察了对二进制及位运算的理解,特别是本题的 n& (n-1)的解法更是精妙的地方。