Leetcode:354. 俄罗斯套娃信封问题(java最长递增子序列LIS)

题目描述:

给你一个二维整数数组 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(nlog⁡n)

空间复杂度:O(n)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值