力扣面试题 17.08. 马戏团人塔

多条件下的最长上升子序列

马戏团人塔
如本题,要求身高、体重分别递增,且都只能<,不能<=

本文将介绍两种写法:

  1. 二分法 : O(nlogN)
    2.常规dp O(n2

本文主要解释:

  • 二分法中的排序要求及原因
  • dp的小优化

若对如何求最长上升子序列完全不了解的,请先参考:最长上升子序列(LIS)算法

二分法

用NlogN的二分查找法求解时,必须按照身高递增排序;身高相同时,体重递减排序,否则可能会出现多个身高相同,而体重递增的答案,则不符合<的要求。

Arrays.sort(person, (a, b) -> a[0] == b[0] ? b[1] - a[1] : a[0] - b[0]);

而原因就是采用二分查找法时,统计数据时无法区分该数据是身高不同还是相同情况下的更大值,而只有不同的情况下才是有效数据

dp[]存储的是对应长度LIS的最小末尾,而不是LIS
如果都采用递增排序,如本题数据,就会出现
[1][4],[2][5],[2][6],[2][7],[3][8],
其中[2][5],[2][6],[2][7]都会算作有效数据,
只有按上述做法,排成
[1][4],[2][7],[2][6],[2][5],[3][8],
才能保证[2][6],[2][5]不会生效,因为[2][7]已经率先“占领了dp的该位子了,后续只会更新该位子上的值,而不会因为[2][6],[2][5]新增数据

 public static int bestSeqAtIndex(int[] height, int[] weight) {
        int len = height.length;
        int[][] person = new int[len][2];
        for (int i = 0; i < len; ++i)
            person[i] = new int[]{height[i], weight[i]};
         //身高递增排序
         //身高相同时,体重递减排序
        Arrays.sort(person, (a, b) -> a[0] == b[0] ? b[1] - a[1] : a[0] - b[0]);
        int[] dp = new int[len];
        int res = 0;
        for (int[] pair : person) {
        //二分搜索
        //不了解可参考:https://blog.csdn.net/Unknownfuture/article/details/104503235
            int i = Arrays.binarySearch(dp, 0, res, pair[1]);
            if (i < 0)
                i = -(i + 1);
            dp[i] = pair[1];
            if (i == res)
                ++res;
        }
        return res;
    }

    public static void main(String[] args) {
        int[] height = {1, 2, 2, 2, 3};
        int[] weight = {4, 7, 5, 6, 8};
        int max=bestSeqAtIndex(height,weight);
        System.out.println(max);
    }

注意:之所以要把数据按照体重递减处理,完全只是为了迎合NlogN的解法特性,如果换成正常动态规划,完全可以直接按身高、体重都递增,因为判断条件不会受到这样排序的影响,见下

dp(超时,但是是正确的)

此时的dp数组的含义:
dp[i] 表示以 i 结尾的最长上升子序列长度

public int bestSeqAtIndex(int[] height, int[] weight) {
	int len = height.length;
    int[][] person = new int[len][2];
    for (int i = 0; i < len; ++i)
        person[i] = new int[]{height[i], weight[i]};
//排序规则为:身高递增、身高相同时,按体重递增
    Arrays.sort(person, (a, b) -> a[0] == b[0] ? a[1] - b[1] : a[0] - b[0]);
    int[] dp = new int[len];
//因为dp数组含义不一样,
//每个i都至少可以以自己结尾,即自己一个元素作为最长上升子序列,长度为1
    Arrays.fill(dp,1);

    int ans = 0;
//这里i从0开始,只是因为求解dp数组的最大值max的逻辑在本循环中,若数组只有一组,则ans=0,错误
//若通过额外一个循环dp数组,这样求解ans,这里就从0 或 1 开始都可以
//当然,直接令ans初值为dp[0]=1 ,然后这里也是从0 或 1 开始都可以,个人代码风格的区别
    for(int i = 0; i<len;i++){
        for(int j=0;j<i;j++){
        //身高、体重都比j位的人大,在j位后“添加”本i位元素作为上升子序列
        //这里就是上文所说的判断条件不会受影响,因为只有严格符合条件的,我们才会尝试更新dp[i]
            if(person[i][0]>person[j][0]&&person[i][1]>person[j][1]){
                dp[i] = Math.max(dp[i],1+dp[j]);
            }
        }
        ans = Math.max(ans,dp[i]);
    }
    return ans;
}
小拓展

dp中可以有一个小优化:(但还是会超时)

  1. 用NlogN中所述的排序:身高递增,身高相同时,体重递减
  2. 判断条件只需要判断体重是否符合条件

原因:假设 i > j ,按此排序规则,有 身高 i >= j

  1. 大于> 时,直接判断体重,这个不解释,就是正常情况
  2. 等于=时,因为体重是递减排序,所以必定不存在体重 i > j 的情况,所以if不满足,dp不会更新

这样就省略了一个判断条件,因此笔者的建议是都按NlogN的排序方式,因为

  • 可以用二分时,就不会出错
  • 用dp时,如果记得就可以省去一些判断条件,就算不记得,使用全条件的性能也一样,不会变差
//本代码只有上述两点区别,其他代码完全一样
public int bestSeqAtIndex(int[] height, int[] weight) {
        int len = height.length;
        int[][] person = new int[len][2];
        for (int i = 0; i < len; ++i)
            person[i] = new int[]{height[i], weight[i]};
		//排序规则,身高递增,身高相同时,体重递减
        Arrays.sort(person, (a, b) -> a[0] == b[0] ? b[1] - a[1] : a[0] - b[0]);
        int[] dp = new int[len];

        Arrays.fill(dp,1);

        int ans = 0;
        for(int i = 0; i<len;i++){
            for(int j=0;j<i;j++){
            //只需要判断体重,而无需判断身高
                if(person[i][1]>person[j][1]){
                    dp[i] = Math.max(dp[i],1+dp[j]);
                }
            }
            ans = Math.max(ans,dp[i]);
        }
        return ans;
    }

同类型题目:
354. 俄罗斯套娃信封问题
上述的O(n2)dp算法可在此题验证,原理无异

建议完成↑题目,加深认识

拓展:堆箱子,也是一种类型的题目,不过这个是三维数组,就不好用NlogN;dp的小优化也可以用在该题


本文完,有误欢迎指出

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值