【LeetCode 程序员面试金典(第 6 版)】第五章题目 05.01 ~ 05.08

本文整理了LeetCode中 程序员面试金典(第 6 版)的练习题的第五章的题目。

面试题 05.01. 插入 - 力扣(LeetCode) (leetcode-cn.com)

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

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

二进制 运算

class Solution {
public:
    int insertBits(int N, int M, int i, int j) {
        for(int k = i; k < j + 1; ++k){
            N &= ~(1 << k);
        }
        M <<= i;
        return N + M;
    }
};

面试题 05.02. 二进制数转字符串 - 力扣(LeetCode) (leetcode-cn.com)

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

本题对应另一个很常见的问题:

现有 n 种面额的金币各一个,想买一个价格为 m 的物品,能否恰好凑齐(不找零钱)?

思路:只需按照从大到小的顺序,依次凑即可。(当然这也可以对应到背包问题中)

举个例子:

金币面额 1 2 4 8 16

物品价格 25

面额从大到小遍历, 16 8 1 这三个面额正好可以凑够 25。10011(二进制表示取或不取)。

思考:为什么需要从大到小遍历?从小到大遍历可以吗?

答案:只能够从大到小遍历。从大到小遍历才可以避免借位的问题。同样拿上述例子,如果从小到大遍历,25 比 1 大,故要,25-1=24(最结果上来看也确实需要)。24 比 2 大,故要,24-2=22(已经发生问题,从结果上来看,并未需要 2,但是由于可以从更高位借值,同样也是减去)。而从大到小遍历则不会发生该问题。

class Solution {
public:
    string printBin(double num) {
        string ans("0.");
        double tmp = 1;
        //从大到小枚举 30位 浮点数二进制:
        // 0. 1   1    1      1    ...
        //   0.5 0.25 0.125 0.0625 ...

        for(int i = 0; i < 30; ++i){
            tmp /= 2;
            if(num >= tmp){
                // 拥有当前二进制位 
                ans += '1';
                num -= tmp;
            }else{
                // 
                ans += '0';
            }
        }
        // 如果可以表示,最后num一定是为 0 
        if(num != 0) return string("ERROR");
        // 去除后导零 
        while(ans.back() == '0') ans.pop_back();
        return ans;
    }// end printBin 
}; // end class 

面试题 05.03. 翻转数位 - 力扣(LeetCode) (leetcode-cn.com)

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

暴力修改每个位置,将其变为 1。针对每一种可能计算每个数的最长连续 1 的个数。

class Solution {
public:
    int reverseBits(int num) {
        short mx = 0;
        // 利用机会将位置 i 变成 1 (不管是否本来就是1)
        for(int i = 0; i < 32; ++i){
            int tmp = num | 1 << i;
            // 计算 tmp 的最长1的长度 
            short cnt = 0; short c[32] = {0};
            c[0] = tmp & 1; cnt = max(cnt , c[0]);
            for(int j = 1; j < 32; ++j){
                // 以j为最后一个字符 最长连续1的长度 
                c[j] += tmp >> j & 1 ? 1 + c[j - 1] : 0;
                // tmp中最长的连续1的长度
                cnt = max(cnt , c[j]);
            }
            //所有尝试修改后,最长连续1的长度
            mx = max(mx, cnt);
        }
        return mx;
    }// end reverseBits
}; // end class 

面试题 05.04. 下一个数 - 力扣(LeetCode) (leetcode-cn.com)

下一个数。给定一个正整数,找出与其二进制表达式中 1 的个数相同且大小最接近的那两个数(一个略大,一个略小)。

本题还是挺复杂的。

为了更方便处理,可以先将整数 x 转化为二进制字符串,(该字符串的低位表示该整数的低位),在字符串上进行处理。

思路

为了找到略大的数字:不难想到,在二进制串中,从低到高中找到第一个 10,把这两个值交换后,显然数字变大了。这就足够了吗?数字确实是变大了,但却不是第一个大于 x 的值。此时,可以发现如果将上述 1 位置处左侧的 0 都提前,可以发现数字变小了,但还是大于 x 的。

同理,为了找到略小的数字:在二进制串中,从低到高中找到第一个 01,把这两个值交换后,此时显然数字变小。但还不够,继续将上述 0 位置处左侧的 1 都提前,此时数字变大,但还是小于 x。

下面的代码中有详细的注释。

class Solution {
public:
    // 逆序的二进制字符串转为int
    int binstr2int(string s){
        int ans = 0;
        for_each(s.rbegin(), s.rend(), [&](char ch){
            ans <<= 1;ans += ch - '0'; 
        });
        return ans;
    }
    vector<int> findClosedNumbers(int num) {
        vector<int>ve{-1,-1};
        if(num == INT32_MAX) return ve;

        // 获取31位的二进制表示 
        string binary("");
        do{
            binary += num % 2 + '0';
            num /= 2;
        }while(num > 0);
        while(binary.size() != 31) binary += '0';

        // find the bigger one 
        int cnt_0 = 0;// 统计在找到10之前有多少0
        string tmp(binary);
        for(int i = 0; i + 1 < tmp.size(); ++i){
            //发现可以提高的位置
            if(tmp[i] == '1' && tmp[i + 1] == '0') {
                //将i处1提高一位(一定是增大num)
                swap(tmp[i + 1], tmp[i]);
                //将i处左侧所有的0提前 (在增大的num的前提下,尽可能减少num )
                for(int j = i - 1; j >= 0; --j){
                    if(cnt_0 > 0) tmp[j] = '0';
                    else tmp[j] = '1';
                    cnt_0 -= !(tmp[i] - '0');
                }
                //赋值
                ve[0] = binstr2int(tmp);
                break;
            }
            cnt_0 += !(tmp[i] - '0');
        }

        //find the smaller one 
        int cnt_1 = 0; // 统计在找到01之前有多少1
        for(int i = 0; i + 1 < binary.size(); ++i){
            //发现可以减少的位置
            if(binary[i] == '0' && binary[i + 1] == '1'){
                //将i+1处1向左移动一位(一定是减少num)
                swap(binary[i], binary[ i + 1]);
                // 将i位置之前的1都提到前面(在减少num的情况下,尽可能增大num)
                for(int j = i - 1; j >= 0; --j){
                    if(cnt_1 > 0) binary[j] = '1';
                    else binary[j] = '0';
                    cnt_1 -= binary[j] - '0';
                }
                ve[1] = binstr2int(binary);
                break;  
            }
            cnt_1 += binary[i] - '0';
        } // end find smaller one 

        return ve;
    }// end findClosedNumbers 
}; // end class 

面试题 05.06. 整数转换 - 力扣(LeetCode) (leetcode-cn.com)

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

  1. A,B 范围在[-2147483648, 2147483647]之间

A 和 B 不相等的位置都需要改变。通过 C= A^B 可以得到不相同的位置。求 C 中包括几个 1 即可。

class Solution {
public:
    int convertInteger(int A, int B) {
	// __builtin_popcount(x) 为内置函数,返回x的二进制中1的个数。
        return (int)__builtin_popcount(A ^ B);
    }
};

面试题 05.07. 配对交换 - 力扣(LeetCode) (leetcode-cn.com)

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

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

该题需要用到 magic number

二进制数的奇数位和偶数位可以通过十六进制表示。如此一个数的所有奇数位可以直接获得,同理所有偶数位也可获得。

随后通过移位可以获得最后答案。

下面代码对整个过程进行详细描述。

class Solution {
public:
    int exchangeBits(int num) {
        // magic number 的构造过程
  
        // even 偶数的构造
        // 0101 0101 0101
        // 5     5    5 

        // odd 奇数的构造
        // 1010 1010 1010
        // a     a    a

        int ans = 0;
	// 交换并赋值给ans 
        // odd 
        // cout << "odd" << (num & 0x55555555)  << "\n";
        ans |= (num & 0x55555555) << 1;
        // even 
        // cout << "even" << (num & 0xaaaaaaaa) << "\n";
        ans |= (num & 0xaaaaaaaa) >> 1;
        return ans;
    }
};  

面试题 05.08. 绘制直线 - 力扣(LeetCode) (leetcode-cn.com)

绘制直线。有个单色屏幕存储在一个一维数组中,使得 32 个连续像素可以存放在一个 int 里。屏幕宽度为 w,且 w 可被 32 整除(即一个 int 不会分布在两行上),屏幕高度可由数组长度及屏幕宽度推算得出。请实现一个函数,绘制从点(x1, y)到点(x2, y)的水平线。

给出数组的长度 length,宽度 w(以比特为单位)、直线开始位置 x1(比特为单位)、直线结束位置 x2(比特为单位)、直线所在行数 y。返回绘制过后的数组。

示例 1:

输入:length = 1, w = 32, x1 = 30, x2 = 31, y = 0
输出:[3]
说明:在第 0 行的第 30 位到第 31 为画一条直线,屏幕表示为[0b000000000000000000000000000000011]

首先题目一定要能够看懂。题意描述的还不够清晰。

思路:

最直观的想法:将属于第 y 行的第一个数字作为起点开始遍历,如果当前数字的某个二进制位在 x1 和 x2 的范围内,说明该二进制位需要修改为 1,否则修改为 0。如此遍历到第 y 行的最后一个数字。

class Solution {
public:
    vector<int> drawLine(int length, int w, int x1, int x2, int y) {
        vector<int>ans(length, 0);
	// cur 遍历第y行的 各个数字,每个数字32位。
	// curInter 记录在第y行中,总共访问过多少二进制位。
        for(int cur = y * w / 32, curInter = 0; cur < (y + 1) * w / 32; ++cur){
            int val = 0;
	    //遍历当前数字的32个二进制位 
            for (int j = 0;  j < 32; ++j){
		// 当前数字的第j个二进制位 在起点和终点范围内,需要修改 
                if(curInter + j >= x1 && curInter + j <= x2) {
                    val |= 1 << (31 - j); 
                }
            }
            curInter += 32; // 准备迭代下一个数字
            ans[cur] = val; // 赋值,cur指向的是数组中的第cur个数字
        }
        return ans;
    }// end drawLine
};// end class 

第二种解法:

只需遍历第 y 行的在 x1 和 x2 范围内的二进制,将其赋值给对应数组中的值。

class Solution {
public:
    vector<int> drawLine(int length, int w, int x1, int x2, int y) {
        vector<int>ans(length, 0);
	// 遍历 直线起点和终点之间的二进制位置 
        for (int p = x1; p <= x2; ++p){
	    // y * w / 32: 获取第y行第一个数字的在数组中的位置
	    // p / 32    :  获取当前二进制位 属于第y行的第几个数字
	    // p % 32    : 表示当前二进制位 位于当前数字的第几位二进制
            ans[y * w / 32 + p / 32] |= 1 << (31 - p % 32);
        }
        return ans;
    }
};

你可能感兴趣的:(LeetCode,leetcode,面试,算法)