题目描述:
给你一个二维整数数组 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)问题
但是和普通的最长递增子序列不同,这里是个二维数组,我们可以以w为第一关键字递增排序,靠后面的h来找到最长递增子序列,但是这里会出现一个问题,如[[1,1],[1,2],[1,3]],这三个信封是不能互相嵌套的,结果应该为1,但是我们会求出3。
这里我们就要杜绝由于w相同导致的问题,我们可以以h作为第二关键字递减排序,这里就是[[1,3],[1,2],[1,1]],此时不管多少个w相同,我们都只会得到递增子序列长度为1,从根本上杜绝了这个问题。
然后问题就转换成了经典的最长递增子序列(LIS)问题,我们只要管排序后的h就行了。
方法一: 动态规划
dp[i]表示以第i个元素结尾最长递增子序列的长度,状态转移方程为dp[i] = dp[j] + 1,其中dp[j]就是前0~i-1个数中,比第i个数小的dp值
例如数组为[10,9,2,5,3,7,101],dp[0]为1(初始化所有的dp为1),然后到9,我们找9之前的数,没有比他小的,那么dp[1] = 1,再看2也一样dp[2] = 1,再看5,发现前面只有一个比5小的2,我们找到他的dp值为1,所以5的dp值dp[3] = 1+1=2,以此类推,到101时,我们发现前面的所有都比他小,我们一样的,遍历101之前的数,找到最大的dp值,且比101小的数。就可以得到结果了dp了
最后我们将dp数组中的最大值取出来,就是我们的结果最大递增子序列的长度。
(leetcode里面有一个离谱的输入会超时,这里学习一下思路)
class Solution {
public int maxEnvelopes(int[][] envelopes) {
int n = envelopes.length;
//排序
Arrays.sort(envelopes, (o1, o2) -> o1[0] == o2[0] ? o2[1] - o1[1] : o1[0] - o2[0]);
int[] dp = new int[n];
int res = 1;
for (int i = 0; i < n; i++) {
//初始化dp为1
dp[i] = 1;
for (int j = 0; j < i; j++) {
//只遍历比当前h小的数
if (envelopes[i][1] > envelopes[j][1]){
//找到这些数中最大的dp值,+1赋值给dp[i]
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
//找出dp数组中的最大值,即为结果
res = Math.max(res, dp[i]);
}
return res;
}
}
时间复杂度:O(n^2)
空间复杂度:O(n^2)
方法二:贪心+二分查找
我们根据上面的思路,同样先排序这个二维数组,只要管h值就行了(将其提出来为一维数组也一样)
d[i]表示,最长递增子序列长度为i时,最后一个数的最小值,例如[1,2,3,4,5,6],我们对于d[4],我们知道最长子序列为4的有[1,2,3,4],[2,3,4,5],[3,4,5,6],那么有三个,我们要找到最后一个数的最小值,显然这三个长度为4的数组中,末尾元素最小的是4,故d[4]=4。
同样的我们可以注意到数组d是单调递增的,也就是d[i] > d[j]时i>j。这里怎么证明呢?我们可以假设,d[i] > d[j] 时 i < j,我们只要证明它是错误的就行了,因为d[j]是对应长度为j的子序列,大于i的话,我们将j变短,缩短为i,也就是将j减去j-i个元素,变得和i一样长。因为说了d[j]是表示长度为j的单调递增子序列,那么在d[j]中i位置的数x肯定比d[i]大或相同(因为d[i]是最小值,这个x不一定是最小值),即x>= d[i],就是说d[j] >x>= d[i],前面又说d[i] > d[j] 故错误。可能有点难理解,举例说明:如对于[1,3,2,4,5,6],d[4] > d[2],4>2是必然,如果说d[2] > d[4],4>2(其实和前面一样,总不能2>4吧),这里d[4]=4(前面也解释过了),d[4]对应的数组为[1,2,4,5]或者[1,3,4,5],可以看到,这个数组的递增的,那么我们把他减去2,和d[2]长度一样。那么x=2或者3;d[2]=2对应的数组又为[1,2],不管怎么样,d[2]是最小值,x就不一定了,就肯定是d[4] > x >= d[2],4>2,证明完成。
然后开始解决问题
我们依次遍历这个h,如果当前h>dp[res](res初始为1),我们将这个数放入dp[++res]即可,否则,就从1~res位置,找到一个位置,大于h的最小值的位置插入(意思就是找到d[res]的最小值),这里可以用二分查找因为这个1~res的数组是有序的,插入结束后,这个数组的长度,即res的值,就是我们求的结果(因为最后res位置那个元素,d[res]就表示长度为res的最大递增子元素的最小值,都说了最长到res了,所以结果就是res了)。
代码如下
class Solution {
public int maxEnvelopes(int[][] envelopes) {
int n = envelopes.length;
//排序
Arrays.sort(envelopes, (o1, o2) -> o1[0] == o2[0] ? o2[1] - o1[1] : o1[0]-o2[0]);
int[] dp = new int[n + 1];
//长度最少为1
int res = 1;
dp[res] = envelopes[0][1];
for (int i = 1; i < n; i++) {
//如果h大于前面的dp值,就将下一个dp值赋值为当前h
if (envelopes[i][1] > dp[res]) {
dp[++res] = envelopes[i][1];
} else {
//pos + 1为最后找到的插入位置。将那个位置的dp最小值替换
int l = 1, r = res, pos = 0;
//二分查找,找到插入位置
while (l <= r) {
int mid = (l + r) >> 1;
if (envelopes[i][1] > dp[mid]) {
pos = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
dp[pos + 1] = envelopes[i][1];
}
}
return res;
}
}
时间复杂度:O(nlogn)
空间复杂度:O(n)