js解leetcode(80)-中等

1.节点与其祖先的最大差值

题目:

给定二叉树的根节点 root,找出存在于 不同 节点 A 和 B 之间的最大值 V,其中 V = |A.val - B.val|,且 A 是 B 的祖先。

(如果 A 的任何子节点之一为 B,或者 A 的任何子节点是 B 的祖先,那么我们认为 A 是 B 的祖先)

思路:在低估时,不断更新最大值和最小值即可

时间复杂度O(n) 空间复杂度O(logn)

var maxAncestorDiff = function (root, min = root?.val, max = root.val) {
  if (!root) return 0;
  min = Math.min(min, root.val);
  max = Math.max(max, root.val);
  return Math.max(
    max - min,
    maxAncestorDiff(root.left, min, max),
    maxAncestorDiff(root.right, min, max)
  );
};

2.两地调度

题目:

公司计划面试 2N 人。第 i 人飞往 A 市的费用为 costs[i][0],飞往 B 市的费用为 costs[i][1]。

返回将每个人都飞到某座城市的最低费用,要求每个城市都有 N 人抵达。

思路:采用贪心的方式决定每个人去哪个地方。标准就是某个人去A和B的费用的绝对值。这个差值越大,说明这个人应该优先安排。

所以计算每个人前往两个地方的费用之差,然后按照差值的大小从大到小排序,类似两个栈,某个栈满了就只推入另一个栈

时间复杂度O(nlogn),空间复杂度O(n)

/**
 * @param {number[][]} costs
 * @return {number}
 */
var twoCitySchedCost = function(costs) {
  const dis = costs.map((item, index) => index);
  dis.sort(
    (a, b) =>
      Math.abs(costs[b][0] - costs[b][1]) - Math.abs(costs[a][0] - costs[a][1])
  );
  let v1 = 0,
    v2 = 0;
  let c = 0;
  const l = costs.length;
  for (let i = 0; i < l; i++) {
    if (v1 < l / 2 && v2 < l / 2) {
      if (costs[dis[i]][0] < costs[dis[i]][1]) {
        c += costs[dis[i]][0];
        v1++;
      } else {
        c += costs[dis[i]][1];

        v2++;
      }
    } else if (v1 < l / 2) {
      c += costs[dis[i]][0];

      v1++;
    } else {
      c += costs[dis[i]][1];

      v2++;
    }
  }
  return c;
};

3.两个非重叠子数组的最大和

题目:

给出非负整数数组 A ,返回两个非重叠(连续)子数组中元素的最大和,子数组的长度分别为 L 和 M。(这里需要澄清的是,长为 L 的子数组可以出现在长为 M 的子数组之前或之后。)

从形式上看,返回最大的 V,而 V = (A[i] + A[i+1] + ... + A[i+L-1]) + (A[j] + A[j+1] + ... + A[j+M-1]) 并满足下列条件之一:

 

0 <= i < i + L - 1 < j < j + M - 1 < A.length, 或
0 <= j < j + M - 1 < i < i + L - 1 < A.length.
 

思路:最开始的思路,是缓存前缀和,然后双重遍历。

时间复杂度O(n2),空间复杂度O(n)

/**
 * @param {number[]} A
 * @param {number} L
 * @param {number} M
 * @return {number}
 */
var maxSumTwoNoOverlap = function(A, L, M) {
  const l = A.length;
  const lList = new Array(l).fill(0);
  const mList = new Array(l).fill(0);
  const sumLeft = new Array(l).fill(0);
  sumLeft[0] = A[0];
  for (let i = 1; i < l; i++) {
    sumLeft[i] = sumLeft[i - 1] + A[i];
  }
  for (let i = 0; i + L - 1 < l; i++) {
    lList[i] = A[i] + sumLeft[i + L - 1] - sumLeft[i];
  }
  for (let i = 0; i + M - 1 < l; i++) {
    mList[i] = A[i] + sumLeft[i + M - 1] - sumLeft[i];
  }
  let max = 0;
  for (let i = 0; i + L + M - 1 < l; i++) {
    for (let j = i + L; j + M - 1 < l; j++) {
      max = Math.max(max, lList[i] + mList[j]);
    }
  }
  for (let i = 0; i + L + M - 1 < l; i++) {
    for (let j = i + M; j + L - 1 < l; j++) {
      max = Math.max(max, mList[i] + lList[j]);
    }
  }
  return max;
};

然后,可以优化一下时间。可以缓存对于下标i时,两个数组分别对应该下标的前缀最大和和后缀最大和

也就是对于每个下标i,leftL[i]表示i左侧的l数组的最大和,对应的就是rightL[i]是l在右侧时,的最大和。同理计算m的最大前缀和和后缀和

这样只需要枚举每一个i,计算以当前的i为M和L的分界时对应的和,然后取最大值

时间复杂度O(n),空间复杂度O(n)

/**
 * @param {number[]} A
 * @param {number} L
 * @param {number} M
 * @return {number}
 */
var maxSumTwoNoOverlap = function(A, L, M) {
  const l = A.length;
  const lList = new Array(l).fill(0);
  const mList = new Array(l).fill(0);
  const sumLeft = new Array(l).fill(0);
  sumLeft[0] = A[0];
  for (let i = 1; i < l; i++) {
    sumLeft[i] = sumLeft[i - 1] + A[i];
  }
  for (let i = 0; i + L - 1 < l; i++) {
    lList[i] = A[i] + sumLeft[i + L - 1] - sumLeft[i];
  }
  for (let i = 0; i + M - 1 < l; i++) {
    mList[i] = A[i] + sumLeft[i + M - 1] - sumLeft[i];
  }
  let max = 0;
  const maxLKLeftList = new Array(l).fill(0);
  for (let i = L - 1; i + 1 <= l; i++) {
    maxLKLeftList[i] = Math.max(maxLKLeftList[i - 1] || 0, lList[i - L + 1]);
  }
  const maxMLeftList = new Array(l).fill(0);
  for (let i = M - 1; i + 1 <= l; i++) {
    maxMLeftList[i] = Math.max(maxMLeftList[i - 1] || 0, mList[i - M + 1]);
  }
  const maxLKRightList = new Array(l).fill(0);
  for (let i = l - L; i >= 0; i--) {
    maxLKRightList[i] = Math.max(maxLKRightList[i + 1] || 0, lList[i]);
  }
  const maxMRightList = new Array(l).fill(0);
  for (let i = l - M; i >= 0; i--) {
    maxMRightList[i] = Math.max(maxMRightList[i + 1] || 0, mList[i]);
  }
  for (let i = Math.min(L - 1, M - 1); i + L < l || i + M < l; i++) {
    max = Math.max(
      max,
      maxLKLeftList[i] + maxMRightList[i + 1],
      maxMLeftList[i] + maxLKRightList[i + 1]
    );
  }
  return max;
};

4.边框着色

题目:

给出一个二维整数网格 grid,网格中的每个值表示该位置处的网格块的颜色。

只有当两个网格块的颜色相同,而且在四个方向中任意一个方向上相邻时,它们属于同一连通分量。

连通分量的边界是指连通分量中的所有与不在分量中的正方形相邻(四个方向上)的所有正方形,或者在网格的边界上(第一行/列或最后一行/列)的所有正方形。

给出位于 (r0, c0) 的网格块和颜色 color,使用指定颜色 color 为所给网格块的连通分量的边界进行着色,并返回最终的网格 grid 。

思路:初看,是一个典型的dfs,不过题目要求只修改边界的颜色,所以重点就是如何判断某个点是不是边界。

我的想法是,第一次dfs不修改矩阵中的值,但是记录边界的点的坐标。所以这时需要用set记录遍历过的点。然后dfs的函数返回该店是否是连通分量,通过四个方向的dfs的返回值,就可以知道当前点是否是边界。

是边界的话,就把当前点推入数组。在dfs完成之后,遍历数组,将颜色修改

时间复杂度O(mn),空间复杂度O(mn)

/**
 * @param {number[][]} grid
 * @param {number} r0
 * @param {number} c0
 * @param {number} color
 * @return {number[][]}
 */
var colorBorder = function(grid, r0, c0, color) {
  const h = grid.length;
  const w = grid[0].length;
  if (grid[r0][c0] === color) return grid;
  const init = grid[r0][c0];
  const points = [];
  const set = new Set();
  const dfs = (i, j) => {
    if (i < 0 || i >= h) return 0;
    if (j < 0 || j >= w) return 0;
    if (set.has(`${i}-${j}`)) return 1;
    if (grid[i][j] !== init) return 0;
    set.add(`${i}-${j}`);
    const count = dfs(i, j + 1) + dfs(i, j - 1) + dfs(i + 1, j) + dfs(i - 1, j);
    if (count < 4) {
      points.push([i, j]);
    }
    return 1;
  };
  dfs(r0, c0);
  points.forEach((item) => {
    grid[item[0]][item[1]] = color;
  });
  return grid;
};

5.不相交的线

题目:

我们在两条独立的水平线上按给定的顺序写下 A 和 B 中的整数。

现在,我们可以绘制一些连接两个数字 A[i] 和 B[j] 的直线,只要 A[i] == B[j],且我们绘制的直线不与任何其他连线(非水平线)相交。

以这种方法绘制线条,并返回我们可以绘制的最大连线数。

思路:和两个字符串的最长公共子序列是一样的,动态规划即可

dp[i][j]表示A中前i个字符和B中前j个字符的连线数量。

时间复杂度O(mn),空间复杂度O(mn)

/**
 * @param {number[]} A
 * @param {number[]} B
 * @return {number}
 */
var maxUncrossedLines = function(A, B) {
  const l1 = A.length;
  const l2 = B.length;
  const dp = new Array(l1 + 1).fill("").map(() => new Array(l2 + 1).fill(0));
  for (let i = 1; i <= l1; i++) {
    for (let j = 1; j <= l2; j++) {
      if (A[i - 1] === B[j - 1]) {
        dp[i][j] = Math.max(dp[i - 1][j - 1] + 1, dp[i][j]);
      } else {
        dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]);
      }
    }
  }
  return dp[l1][l2];
};

压缩一下

时间复杂度O(mn),空间复杂度O(m)

/**
 * @param {number[]} A
 * @param {number[]} B
 * @return {number}
 */
var maxUncrossedLines = function(A, B) {
  const l1 = A.length;
  const l2 = B.length;
  const dp1 = new Array(l2 + 1).fill(0);
  const dp2 = new Array(l2 + 1).fill(0);
  for (let i = 1; i <= l1; i++) {
    for (let j = 1; j <= l2; j++) {
      if (A[i - 1] === B[j - 1]) {
        dp2[j] = dp1[j - 1] + 1;
      } else {
        dp2[j] = Math.max(dp2[j - 1], dp1[j]);
      }
    }
    for (let j = 1; j <= l2; j++) {
      dp1[j] = dp2[j];
      dp2[j] = 0;
    }
  }
  return dp1[l2];
};