俄罗斯套娃信封问题|刷题打卡

255 阅读2分钟

给定一些标记了宽度和高度的信封,宽度和高度以整数对形式 (w, h) 出现。当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。

请计算最多能有多少个信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。

说明: 不允许旋转信封。

示例:

输入: envelopes = [[5,4],[6,4],[6,7],[2,3]]
输出: 3 
解释: 最多信封的个数为 3, 组合为: [2,3] => [5,4] => [6,7]

解题思路

因为下一个信封能符合要求的话,必须宽度和高度都比之前的大。第一时间想到把宽度排序。比如例子中的信封顺序就变成了:

[[2,3],[5,4],[6,4],[6,7]]

这样从前往后,比如宽度会越来越大。那就只需要考虑在这个顺序下,高度从小到大能取到最长的数量是多少,也就是

[3,4,4,7]

保持递增的话,最长的子数列是多少。 这个求取方法可以用动态规划的方法来求,即求出每个位置开始的子列上,能取到最长的递增子列长度是多少,然后求取最大值:

 const dp = new Array(n).fill(1);
  for (let i = 0; i < arr.length; i++) {
    for (let j = 0; j < i; j++) { 
      if (arr[j] < arr[i]) dp[i] = Math.max(dp[i], dp[j] + 1);
    }
  }

但是有可能有宽度相同的情况,比如例子变成:

[[2,3],[5,4],[6,5],[6,7]]

这样出来的数组可能就是

[3,4,5,7]

会发现最长递增数组的长度是4,但是因为57 的信封的宽度都是 6,只能选一个,所以就会带入一个错误的答案。

解决方法是把每个宽度相同的信封的高度倒序排列:

[[2,3],[5,4],[6,7],[6,5]]

这样出来的高度数组是:

[3,4,7,5]

就可以保证在考虑一个高度值作为目标求最长子序列的时候,相同宽度里的其他高度值就自动被过滤掉了。

完整代码:

/**
 * @param {number[][]} envelopes
 * @return {number}
 */
var maxEnvelopes = function(envelopes) {
  const n = envelopes.length;
  if (n === 0) return 0;

  const sortedArr = envelopes.sort((e1, e2) => {
    if (e1[0] !== e2[0]) return e1[0] - e2[0];
    return e2[1] - e1[1];
  }).map(e => e[1]);

  const dp = new Array(n).fill(1);
  for (let i = 0; i < n; i++) {
    for (let j = 0; j < i; j++) { 
      if (sortedArr[j] < sortedArr[i]) dp[i] = Math.max(dp[i], dp[j] + 1);
    }
  }

  return Math.max.apply(null, dp);
};

总结

这道题看起来有些摸不到头脑,但是仔细分析一下就可以固定其中一个维度,简化成另外一个问题。前置知识是求数组的递增子数组,需要更熟悉动态规划求解问题的方式。

本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情