JavaScript LeetCode Diary 5

209 阅读3分钟

2020-04-14 (DP)

春天到了,走在北京街头,满大街的杨絮迎面扑来,仿佛生存在培养液中。

猎头和 HR 活动的也更频繁了,看来复工的公司多起来了。

338. Counting Bits

在纸上写一下推到公式,从 0 写到 8 就可以发现规律。2,4,8 的 ‘1’是一致的,6,3是一致的。所以可以得到推导公式 dp[n] = dp[n >> 1] + (n & 1)n & 1是判断最右边是否有 1, 防止 bit right shift 时最右侧的 1 算掉了。

/**
 * @param {number} num
 * @return {number[]}
 */
var countBits = function (num) {
  const arr = new Array(num + 1);
  arr[0] = 0;

  for (let i = 1; i <= num; i++) {
    arr[i] = arr[i >> 1] + (i & 1);
  }

  return arr;
};

304. Range Sum Query 2D - Immutable

本来打算做 1314 那道题。题目看不明白,有人说是 304 的变种,那就先做 304。明天再做 1314。

关于 2 维数组的 dp 生成,可以总结一个固定模板,第一行,第一列和剩下的,总共三部分。代码很类似。

这个卡了很久的地方是 sumRegion 的 corner case,当左上端点(row1,col1) 就在边界时,要判断一下,如果在边界上则不用多减了。

/**
 * @param {number[][]} matrix
 */
var NumMatrix = function (matrix) {
  const r = matrix.length;
  if (r === 0) return 0;
  const c = matrix[0].length;
  const dp = matrix.slice();

  for (let i = 0; i < r; i++) {
    for (let j = 0; j < c; j++) {
      if (i === 0 && j !== 0) {
        // 加和第一行
        dp[i][j] = dp[i][j - 1] + matrix[i][j];
      } else if (i !== 0 && j === 0) {
        // 加和第一列
        dp[i][j] = dp[i - 1][j] + matrix[i][j];
      } else if (i > 0 && j > 0) {
        // 加和其他所有
        dp[i][j] = dp[i - 1][j] + dp[i][j - 1] - dp[i - 1][j - 1] + matrix[i][j];
      }
    }
  }
  this.dp = dp;
};

/**
 * @param {number} row1
 * @param {number} col1
 * @param {number} row2
 * @param {number} col2
 * @return {number}
 */
NumMatrix.prototype.sumRegion = function (row1, col1, row2, col2) {
  const { dp } = this;
  const total = dp[row2][col2];
  const left = col1 - 1 >= 0 ? dp[row2][col1 - 1] : 0;
  const top = row1 - 1 >= 0 ? dp[row1 - 1][col2] : 0;
  const overlap = row1 - 1 >= 0 && col1 - 1 >= 0 ? dp[row1 - 1][col1 - 1] : 0;

  return total - left - top + overlap;
};

/**
 * Your NumMatrix object will be instantiated and called as such:
 * var obj = new NumMatrix(matrix)
 * var param_1 = obj.sumRegion(row1,col1,row2,col2)
 */

746. Min Cost Climbing Stairs

刷这题之前,先回顾一下爬楼梯(70 题)。生成一个简单的 Fibonacci 数列(写了一遍,还是第一直觉用的递归,不出所料,超时了。然后看了两年前的提交,也是一开始用的递归,同样的超时。老复读机了。)

这道题在爬楼梯的基础上增加了一步,原来我们需要记录的在每一个台阶时爬楼梯的方法,现在我们需要记录的是爬到当前台阶时的最小开销。有了这个思想,问题迎刃而解。最后 return 的时候,返回倒数第一或倒数第二级台阶的最小开销。

我习惯不去污染 input,所以创建一个新的数组记录 dp 的数值。其实是可以 inplace 修改 cost,这样空间更小。

/**
 * @param {number[]} cost
 * @return {number}
 */
var minCostClimbingStairs = function (cost) {
  if (cost.length <= 2) return Math.min.apply(null, cost);

  const len = cost.length;
  const dp = cost.slice();
  let i = 2;

  while (i < len) {
    dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i];
    i++;
  }
  return Math.min(dp[len - 1], dp[len - 2]);
};