面试题 1:整数除法

题目:输入 2 个 int 型整数,它们进行除法计算并返回商,要求不得使用乘号'*'、除号'/'及求余符号'%'。当发生溢出时,返回最大的整数值。假设除数不为 0。例如,输入 15 和 2,输出 15/2 的结果,即 7。

  1. 减法实现除法

被除数依次减去 2

/**
 * @param {number} a
 * @param {number} b
 * @return {number}
 */
var divide = function (a, b) {
  if (a == -2147483648 && b === -1) return 2 ** 31 - 1;

  let res = 0;
  let nagative = 2;
  if (a > 0) {
    a = -a;
    nagative -= 1;
  }

  if (b > 0) {
    b = -b;
    nagative -= 1;
  }
  let val = a;
  while (val <= b) {
    val -= b;
    res++;
  }
  return nagative == 1 ? -res : res;
};

时间复杂度 O(n)

/**
 * @param {number} a
 * @param {number} b
 * @return {number}
 */
var divide = function (a, b) {
  if (a == -2147483648 && b === -1) return 2 ** 31 - 1;

  let nagative = 2;
  if (a > 0) {
    a = -a;
    nagative -= 1;
  }

  if (b > 0) {
    b = -b;
    nagative -= 1;
  }
  const res = dividecore(a, b);
  return nagative == 1 ? -res : res;
};

var dividecore = function (a, b) {
  let result = 0;
  while (a <= b) {
    let value = b;
    let quotient = 1;
    while (a <= value << 0) {
      quotient += quotient;
      value += value;
    }
    result += quotient;
    a -= value;
  }
  return result;
};
  • 利用位运算

本质上还是用减法

image-20220221234633480

/**
 * @param {number} a
 * @param {number} b
 * @return {number}
 */
var divide = function (a, b) {
  const MAX = Math.pow(2, 31) - 1,
    MIN = -Math.pow(2, 31);
  if (a == MIN && b == -1) return MAX;
  if (a == MIN && b == 1) return MIN;

  const sign = (a > 0) ^ (b > 0);
  (a = Math.abs(a)), (b = Math.abs(b));
  let n = 0;
  for (let i = 31; i >= 0; i--) {
    if (a >>> i >= b) {
      a -= b << i;
      n += 1 << i;
    }
  }
  return sign ? -n : n;
};

技巧

  • 判断符号为正或负数
let nagative = 2;
if (a > 0) {
  nagative -= 1;
}

if (b > 0) {
  nagative -= 1;
}

若 nagative 为 1,则为负数

或则利用异或判断

// 1 ^ 1 = 0,0 ^ 0 = 0,1 ^ 0 = 1
const sign = (a>0)^(b>0))

sign = (a > 0) ^ (b > 0),为 1 时,表明是负数,为 0 时,表明是正数

二进制

整数在计算机中是以二进制的形式表示的。二进制是指数字的每位都是 0 或 1。例如,十进制形式的 2 转化为二进制形式之后是 10,而十进制形式的 10 转换成二进制形式之后是 1010。

面试题 2:二进制加法

给定两个 01 字符串 a 和 b ,请计算它们的和,并以二进制字符串的形式输出。

输入为 非空 字符串且只包含数字 1 和 0。

  1. 进制转换
    先将 aa 和 bb 转化成十进制数,求和后再转化为二进制数。
class Solution {
    public String addBinary(String a, String b) {
        return Integer.toBinaryString(
            Integer.parseInt(a, 2) + Integer.parseInt(b, 2)
        );
    }
}

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/JFETK5/solution/er-jin-zhi-jia-fa-by-leetcode-solution-fa6t/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

这种解法可能会导致溢出

  1. 模拟二进制运算
/**
 * @param {string} a
 * @param {string} b
 * @return {string}
 */
var addBinary = function (a, b) {
  let al = a.length - 1;
  let bl = b.length - 1;
  let res = [];
  let e = 0;

  while (al >= 0 || bl >= 0) {
    const c = a.charAt(al) || 0;
    const d = b.charAt(bl) || 0;
    if (parseInt(c) + parseInt(d) + e > 2) {
      res.unshift(1);
      e = 1;
    } else if (parseInt(c) + parseInt(d) + e > 1) {
      res.unshift(0);
      e = 1;
    } else {
      res.unshift(parseInt(c) + parseInt(d) + e);
      e = 0;
    }
    al--;
    bl--;
  }
  // 防止剩余进一位
  if (e !== 0) {
    res.unshift(e);
  }

  return res.join("");
};

别人的代码,简化了中间的判断

var addStrings = function(num1, num2) {
    let res = ''
    let i1 = num1.length - 1
    let i2 = num2.length - 1
    let carry = 0
    while (i1 >= 0 || i2 >= 0) {
        const x = i1 >= 0 ? num1[i1] - '0' : 0
        const y = i2 >= 0 ? num2[i2] - '0' : 0

        const sum = x + y + carry
        res += (sum % 2)
        carry = Math.floor(sum / 2)

        i1--
        i2--
    }
    if (carry) res += carry
    return res.split("").reverse().join("")
};

作者:tangweiqun
链接:https://leetcode-cn.com/problems/JFETK5/solution/jian-dan-yi-dong-javacpythonjs-pei-yang-r6bem/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  1. 位运算
    需要位运算基础?

面试题 3:前 n 个数字二进制形式中 1 的个数

题目:输入一个非负数 n,请计算 0 到 n 之间每个数字的二进制形式中 1 的个数,并输出一个数组。例如,输入的 n 为 4,由于 0、1、2、3、4 的二进制形式中 1 的个数分别为 0、1、1、2、1,因此输出数组[0,1,1,2,1]。

使用一个 for 循环来计算从 0 到 n 的每个整数 i 的二进制形式中 1 的个数。于是问题转换成如何求一个整数 i 的二进制形式中 1 的个数。

  • 简单计算每个整数的二进制形式中 1 的个数
    计算整数 i 的二进制形式中 1 的个数有多种不同的方法,其中一种比较高效的方法是每次用“i&(i-1)”将整数 i 的最右边的 1 变成 0。整数 i 减去 1,那么它最右边的 1 变成 0。如果它的右边还有 0,则右边所有的 0 都变成 1,而其左边所有位都保持不变。下面对 i 和 i-1 进行位与运算,相当于将其最右边的 1 变成 0。以二进制的 1100 为例,它减去 1 的结果是 1011。1100 和 1011 的位与运算的结果正好是 1000。二进制的 1100 最右边的 1 变为 0,结果刚好就是 1000。
/**
 * @param {number} n
 * @return {number[]}
 */
var countBits = function (n) {
  let res = [];
  for (let i = 0; i <= n; i++) {
    let j = i;
    let m = 0;
    while (j != 0) {
      j = j & (j - 1);
      m++;
    }
    res[i] = m;
  }
  return res;
};

如果一个整数共有 k 位,那么它的二进制形式中可能有 O(k)个 1。在上述代码中,while 循环中的代码对每个整数将执行 O(k)次,因此,上述代码的时间复杂度是 O(nk)。

优化

/**
 * @param {number} n
 * @return {number[]}
 */
var countBits = function (n) {
  let res = new Array(n + 1).fill(0);
  for (let i = 1; i <= n; i++) {
    res[i] = res[i & (i - 1)] + 1;
  }
  return res;
};

时间反而更久,没看懂...

  • 根据“i/2”计算 i 的二进制形式中 1 的个数

面试题 4:只出现一次的数字

给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。

示例 1:

输入:nums = [2,2,3,2]
输出:3

第一反应就是用 map

  • 哈希表
/**
 * @param {number[]} nums
 * @return {number}
 */
var singleNumber = function (nums) {
  let map = {};
  for (let i = 0; i < nums.length; i++) {
    if (map[nums[i]] !== undefined) {
      map[nums[i]] += 1;
    } else {
      map[nums[i]] = 1;
    }
  }

  for (let key in map) {
    if (map[key] === 1) {
      return key;
    }
  }
};

leetCode 其他位运算题目

总结

编程语言(如 Java)可能定义了多种占据不同内存空间的整数类型,内存空间不同的整数类型的值的范围也不相同。

位运算是对二进制整数的运算,包括与运算、或运算、非运算、异或运算、左移运算和右移运算。只有深刻理解每种位运算的特点才能在需要的时候灵活地应用合适的位运算解决相应的问题。

参考文章


看见了
876 声望16 粉丝

前端开发,略懂后台;