力扣中剑指offer第一部分是Java位运算相关的,虽然在日常开发中,我们不常用位运算,但是考虑性能的话,有的时候用位运算会大大提高。下面结合题,来分享几个位运算相关的技巧:
剑指 Offer II 001. 整数除法
题干:
给定两个整数 a
和 b
,求它们的除法的商 a/b
,要求不得使用乘号 '*'
、除号 '/'
以及求余符号 '%'
。
注意:
整数除法的结果应当截去(truncate)其小数部分,例如:truncate(8.345) = 8 以及 truncate(-2.7335) = -2。 假设我们的环境只能存储 32 位有符号整数,其数值范围是 [−231, 231−1]。本题中,如果除法结果溢出,则返回 231 − 1
示例 1:
输入:a = 15, b = 2
输出:7
解释:15/2 = truncate(7.5) = 7
示例 2:
输入:a = 7, b = -3
输出:-2
解释:7/-3 = truncate(-2.33333..) = -2
示例 3:
输入:a = 0, b = 1
输出:0
示例 4:
输入:a = 1, b = 1
输出:1
分析:
此题主要就是用加减法去实现除法,要注意32位溢出的情况,以及超时的情况。我计划用减法去模拟,如果不做特殊处理,会出现超时,所以,需要对迭代的步幅作处理。
参考答案
// 因为将 -2147483648 转成正数会越界,但是将 2147483647 转成负数,则不会
// 所以,我们将 a 和 b 都转成负数
class Solution {
public int divide(int a, int b) {
if(a == Integer.MIN_VALUE && b == -1){
return Integer.MAX_VALUE;
}
int sign = (a>0)^(b>0) ? -1 : 1; // ^为异或运算
if(a>0) a = -a;
if(b>0) b = -b;
int res = 0;
while(a <= b){
int value = b; // a能减去b的最大倍
int k = 1; // value包含b的个数
// 0xc0000000 是十进制 -2^30 的十六进制的表示
// 判断 value > 0xc0000000 的原因:保证 value + value < -2^31 不会溢出
// 可以这样判断的原因是:0xc0000000 是最小值 -2^31 的一半,
// 而 a 的值不可能比 -2^31 还要小,所以 value 不可能比 0xc0000000 小
// -2^31 / 2 = -2^30
while(value > 0xc0000000 && a < value + value){
value += value;
k += k;
}
a -= value;
res += k;
}
return sign == 1 ? res: -res;
}
}
知识点:
- ^为异或运算
- Integer.MAX_VALUE为2^31-1, Integer.MIN_VALUE为-2^31
剑指 Offer II 002. 二进制加法
题干:
给定两个 01 字符串 a
和 b
,请计算它们的和,并以二进制字符串的形式输出。
输入为 非空 字符串且只包含数字 1
和 0
。
示例 1:
输入: a = "11", b = "10"
输出: "101"
示例 2:
输入: a = "1010", b = "1011"
输出: "10101"
分析:
按照二进制加法去模拟这个过程,此题关键是char与int类型的转化。
参考答案
class Solution {
public String addBinary(String a, String b) {
StringBuffer ans = new StringBuffer(); // 对字符串操作最好用StringBuffer或StringBuilder
int alen = a.length();
int blen = b.length();
int temp = 0; // 进位
int maxLen = Math.max(alen,blen);
for(int i=0; i<maxLen; ++i){
int t1 = i<alen ? (a.charAt(alen-i-1) - '0') : 0;
int t2 = i<blen ? (b.charAt(blen-i-1) - '0') : 0;
int sum = t1 + t2 + temp;
if(sum > 1){
temp = sum/2;
sum = sum%2;
}else{
temp = 0;
}
ans.append((char) (sum + '0'));
}
if(temp != 0){
ans.append('1');
}
ans.reverse();
return ans.toString();
}
}
知识点:
- char类型转为int类型,
'4'-'0' => 4
- int类型转为char类型,
4+'0' => '4'
剑指 Offer II 003. 前 n 个数字二进制中 1 的个数
题干:
给定一个非负整数 n
****,请计算 0
到 n
之间的每个数字的二进制表示中 1 的个数,并输出一个数组。
示例 1:
输入: n = 2
输出: [0,1,1]
解释:
0 --> 0
1 --> 1
2 --> 10
示例 2:
输入: n = 5
输出: [0,1,1,2,1,2]
解释:
0 --> 0
1 --> 1
2 --> 10
3 --> 11
4 --> 100
5 --> 101
分析:
此题最简单的思路就是,先从0-n遍历一遍,对每个i,都算出其转化为二进制有几个1,然后将结果输出为一个数组,如下方法一。但这个方法时间复杂度高,为O(n*sizeof(integer))
。
方法二就是在线性时间 O(n)
内用一趟扫描做到。此题,我们可以将所有的数分为奇数和偶数来处理:
如果当前num为奇数,则二进制中1的个数为前一个数二进制1的个数+1。 当前数为奇数,代表前一个数为偶数,所以前一个数的二进制最后一位一定是0,当前的数就是在它原来的基础上加了个1。例如,5(101),上一个数为4(100), 5 = 4 + 1, 加上的1就是加到了二进制中的最后一位。
如果当前num为偶数,则二进制中1的个数为 num/2 的二进制中1的个数。 这一灵感来源于位运算。例如 (1)0001 左移一位后变为 2(0010),2(0010)左移一位后变为4(0100),他们1的个数都是不变的,只是位置在变化。
参考答案
// 方法一:
class Solution {
public int[] countBits(int n) {
int ans[] = new int[n+1];
for (int i=0; i<=n; i++){
ans[i] = count1(i);
}
return ans;
}
public int count1(int n) {
int res = 0;
while(n > 1){
if(n%2 != 0){
res++;
}
n = n/2;
}
return n==0 ? 0 : ++res;
}
}
// 方法二:
class Solution {
public int[] countBits(int n) {
int ans[] = new int[n+1];
for (int i=0; i<=n; i++){
if(i % 2 == 0){ // 偶数
ans[i] = ans[i>>1]; // 相当于i/2
}else{ // 奇数
ans[i] = ans[i-1] + 1;
}
}
return ans;
}
}
知识点:
- 左移1位相当于该数乘以2,左移2位相当于该数乘以2*2=4, 15<<2=60,即乘了4。但此结论只适用于该数左移时被溢出舍弃的高位中不包含1的情况。
- 右移1位相当于该数除以2。例如4>>2 = 2