2021/3/25 俄罗斯套娃信封问题
信封嵌套问题
http://www.nowcoder.com/questionTerminal/9bf77b5b018d4d24951c9a7edb40408f
题目描述
描述转载自力扣《354. 俄罗斯套娃信封问题》
给你一个二维整数数组 envelopes ,其中 envelopes[i] = [wi, hi] ,表示第 i 个信封的宽度和高度。
当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。
请计算 最多能有多少个 信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。
注意:不允许旋转信封。
示例1
输入:envelopes = [[5,4],[6,4],[6,7],[2,3]]
输出:3
解释:最多信封的个数为 3, 组合为: [2,3] => [5,4] => [6,7]。
示例2
输入:envelopes = [[1,1],[1,1],[1,1]]
输出:1
解题思路
- 这是一个线性动态规划的单串问题,这道题目是建立在求 LIS 长度的基础之上的。做这道题之前强烈建议先看一下我之前的文章《最长递增子序列》 里面的题。
- 我们可以把每个信封的宽从小到大排列,找宽度的最长递增子序列。然后在宽度顺序不变的情况下再将高度进行一次排序,找最长递增子序列。看看是不是有什么眉目了?
- 我们可以使用使用快排,先对宽度进行排序,再对高度进行排序,然后选出 LIS,但实际测试证明,即使是快排,这种做法也相当耗时,我们需要考虑另一种办法。
- 我们使用 Java 的 Arrays.sort() 进行排序,宽度从小到大排序,如果宽度一样,高度从大到小排序。
这样排序有个好处,就是从前往后,宽度必然是会越来越大的,而宽度相同时,高度又会越来越小,导致无法组成 LIS,这样就限制了宽度相同的信无法套在一起,然后我们就可以无视宽度,只考虑高度。而只考虑高度的情况下,我们只需要和《最长递增子序列》 中做同样的事情,求 LIS 长度即可。
Java代码实现
- 快排法(不推荐,仅供参考)
class Solution {
public int maxEnvelopes(int[][] envelopes) {
int len = envelopes.length;
// 宽度排序
wSort(envelopes, 0, len - 1);
// 高度排序
hSort(envelopes);
// 比较 i-1 个信封的宽高
int max = 1;
int[] dp = new int[len];
Arrays.fill(dp, 1);
for (int i = 1; i < len; ++i) {
for (int j = 0; j < i; ++j) {
if (isUnsuitable(envelopes, i, j)) continue;
int cur = dp[j] + 1;
if (dp[i] < cur) dp[i] = cur;
}
max = dp[i] > max ? dp[i] : max;
}
return max;
}
private boolean isUnsuitable(int[][] arr, int i, int j) {
return arr[j][0] >= arr[i][0] || arr[j][1] >= arr[i][1];
}
private void wSort(int[][] arr, int left, int right) {
sortHelper(arr, left, right, 0);
}
private void hSort(int[][] arr) {
int left = 0, right = 0;
while (right < arr.length) {
while (right + 1 < arr.length && arr[right + 1][0] == arr[left][0]) ++right;
sortHelper(arr, left, right, 1);
++right;
left = right;
}
}
private void sortHelper(int[][] arr, int left, int right, int cdn) {
if (left < right) {
int pivot = qSort(arr, left, right, cdn);
sortHelper(arr, left, pivot - 1, cdn);
sortHelper(arr, pivot + 1, right, cdn);
}
}
private int qSort(int[][] arr, int left, int right, int cdn) {
int[] pivot = arr[left]; // 中轴值
while (left < right) {
while (left < right && arr[right][cdn] >= pivot[cdn]) --right;
if (left < right) arr[left++] = arr[right];
while (left < right && arr[left][cdn] <= pivot[cdn]) ++left;
if (left < right) arr[right--] = arr[left];
}
// 替换中轴
arr[left] = pivot;
return left;
}
} - 推荐解法
import java.util.*;
public class Solution {
public int maxLetters (int[][] letters) {
// 宽度从小到大排序,宽度相同时高度从大到小排序
Arrays.sort(letters, new Comparator<int[]>(){
public int compare(int[] arr1, int[] arr2) {
if (arr1[0] == arr2[0]) return arr2[1] - arr1[1];
else return arr1[0] - arr2[0];
}
});
// h 数组存储高度,抹去了宽度
int[] h = new int[letters.length];
// 存储以 h[i] 结尾的 LIS 长度
int[] dp = new int[letters.length];
// 假设最坏的情况是没有递增,所以 LIS 长度最长为 1
Arrays.fill(dp, 1);
for (int i = 0; i < h.length; ++i) {
h[i] = letters[i][1];
}
// 记录最长 LIS 长度
int max = 1;
for (int i = 1; i < dp.length; ++i) {
for (int j = 0; j < i; ++j) {
// 以 h[i] 结尾的递增子序列, h[j] 必然要比 h[i] 小,否则跳过
if (h[j] >= h[i]) continue;
int temp = dp[j] + 1;
if (dp[i] < temp) dp[i] = temp;
}
max = dp[i] > max ? dp[i] : max;
}
return max;
}
} 