LeetCode-Java位运算相关

574 阅读4分钟

力扣中剑指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;
    }
}

知识点:

  1. ^为异或运算
  2. 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();
    }
}

知识点:

  1. char类型转为int类型,'4'-'0' => 4
  2. 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. 左移1位相当于该数乘以2,左移2位相当于该数乘以2*2=4, 15<<2=60,即乘了4。但此结论只适用于该数左移时被溢出舍弃的高位中不包含1的情况。
  2. 右移1位相当于该数除以2。例如4>>2 = 2