常见算法题目:二进制

284 阅读4分钟

前言

如果想进大厂工作,除了基础知识,项目经验,还有一些非常重要的能力需要锻炼,其中最重要的就是算法。算法题千千万,但是类型也就几十种,接下来的系列文章,我将面试中常见的算法题类型进行归纳。


二进制面试题01. 插入

给定两个整型数字 N 与 M,以及表示比特位置的 i 与 j(i <= j,且从 0 位开始计算)。

编写一种方法,使 M 对应的二进制数字插入 N 对应的二进制数字的第 i ~ j 位区域,不足之处用 0 补齐。具体插入过程如图所示。

1610104070-NuLVQi-05 01

代码:

public int insertBits(int N, int M, int i, int j) {
        // left in (,j+1];middle in [i,j];right in [i-1,0];
        int left = N>>j>>1; // 把最左边的部分调整好了,即抛弃了替换部分和低位部分
        left = left<<j<<1;  // 因此最后要进行或运算,所以把他再移到原来的高位上。
        int middle = M<<i;  // 替换N的j<-----i位,那么只需要将M左移i位即可
        int right = N&((1<<i)-1);// 只需要N的低位,将高位置零,(1<<2)-1 = (11)2
        return left | middle | right;
    }

二进制面试题02. 二进制数转字符串

二进制数转字符串。给定一个介于0和1之间的实数(如0.72),类型为double,打印它的二进制表达式。如果该数字无法精确地用32位以内的二进制表示,则打印“ERROR”。

进制表示,则打印“ERROR”。

1


说明:
1

2


代码:

class Solution {
  public String printBin(double num) {
    StringBuilder ans = new StringBuilder("0.");
    while (num != 0D && ans.length() <= 32) {
      num *= 2;
      if (num >= 1) {
        ans.append(1);
        num -= 1;
      } else ans.append(0);
    }
    return ans.length() > 32 ? "ERROR" : ans.toString();
  }
}

二进制面试题03. 翻转数位

给定一个32位整数 num,你可以将一个数位从0变为1。请编写一个程序,找出你能够获得的最长的一串1的长度。

示例 1:
输入: num = 1775(110111011112)
输出: 8

示例 2:
输入: num = 7(01112)
输出: 4


思路:
本题使用动态规划解决比较容易:使用用两个动态规划数组 current[] reverse[]
current[i] 表示包含第i位的从num二进制低位至第i位连续1的最长长度
reverse[i] 表示包含第i位的从低位到第i位最多翻转1个0->1 的连续1的最长长度

用num[i]表示整数num第i位的值

当num[i]=1时,current[i] = current[i-1]+1,因为current[i-1]一定包含i-1位,也就是和第i位连续,所以前i-1的最大长度连上第i位的长度就等于current[i],同理reverse[i] = reverse[i-1]+1;

num[i]=0时,连续中断,current[i]=0,而reverse[i]允许翻转1次,但是reverse[i]又必须包含第i位,也就是说只能翻转第i位,所以前面不能出现翻转,必须全是1,这个长度恰好就是current[i-1],所以reverse[i] = current[i-1]+1

遍历num所有位数,也就是32位后,reverse数组中的最大值就是答案。

状态方程:
current[i] = num[i]==1?current[i-1]+1:0
reverse[i] = num[i]==1?reverse[i-1]+1:current[i-1]+1

观察状态方程,我们发现current和reverse第i位只和第i-1位有关,所以可以把动态数组优化成两个变量current和reverse,同时更新最大值max并作为结果返回。

解法只说明答案是取reverse最大值而不涉及current最大值,可以思考一下为什么。 该解法针对任何值均需要循环32次,然而一些比较小的数显然不用遍历32次(比如1)。想一想可不可以优化代码用以对某些小值减少循环次数。


代码:

class Solution {
    public int reverseBits(int num) {
        int max = 0;
        int reverse = 0;
        int current = 0;
        for(int i=0;i<32;i++){
            if((num&1)==1){
                current++;
                reverse++;
            }else{
                reverse = current+1;
                current = 0;
            }
            if(reverse>max) max = reverse;// 每遍历1位,reverse就会更新一下
            num >>= 1;
        }
        return max;
    }
}

二进制面试题04. 整数转换

整数转换。编写一个函数,确定需要改变几个位才能将整数A转成整数B。

示例1:
输入:A = 29 (或者0b11101), B = 15(或者0b01111)
输出:2

示例2:
输入:A = 1,B = 2
输出:2
提示:
A,B范围在[-2147483648, 2147483647]之间

解题思路:
(1)A与B异或,得到的结果可以用来判断不同的位数,例如:A = 1100, B = 0011, A^B = 1111 我们知道A^B中有几个1,那么就可以判断出有几个位需要转换了
(2)emp&(temp-1) 可以把temp最右边的1置为0,而其他位保持不变,例如:
temp=1100,
temp-1=1100-1=1011
temp&(temp-1) = 1100 & 1011 = 1000

代码:

public int convertInteger(int A, int B) {
        // A与B进行异或运算,得到的结果1就是不相同,0就是相同
        // 0011^1100 = 1111 结果有几个1,那么就有几个位要改变
        int temp = A^B,count = 0;
        while (temp != 0){
            // temp&(temp -1) 可以把temp最右边的1置0,其余位不变
            // 这样,这个操作执行了几次,就是有几位要转换
            temp = temp&(temp - 1);
            count++;
        }
        return count;
    }

二进制面试题05. 配对交换

配对交换。编写程序,交换某个整数的奇数位和偶数位,尽量使用较少的指令(也就是说,位0与位1交换,位2与位3交换,以此类推)。

示例1:

输入:num = 2(或者0b10)

输出 1 (或者 0b01)

示例2:

输入:num = 3

输出:3

提示:

num的范围在[0, 2^30 - 1]之间,不会发生整数溢出。

class Solution {
    public int exchangeBits(int num) {
        int[] arr = new int[31];
        int bit = 0;
        while (num != 0) {
            arr[bit++] = num % 2;
            num = num / 2;
        }

        for (int i = 1; i <= bit; i = i + 2) {
            int temp = arr[i];
            arr[i] = arr[i - 1];
            arr[i - 1] = temp;
        }

        int result = 0;
        for(int i = bit; i >= 0;i--){
            result = result * 2 + arr[i];
        }
        return result;
    }
}