leetcode673 最长递增子序列的个数 中等

题目:
在这里插入图片描述
思路:
先来回顾动态规划求最长递增子序列长度

    //动态规划
   static int lengthOfLIS(int[] nums) {
        if(nums == null || nums.length == 0){
            return 0;
        }
        //整个问题的最优解,一开始至少有一个
        int maxLen = 1;
        int[] dp = new int[nums.length];
        dp[0] = 1;
        for(int i = 1; i < nums.length; i++){
            //记录当前位置i之前的最大上升子序列长度
            int currentMax = 0;
            for(int j = 0; j < i; j++){
                //只有比之前位置的元素大才进行currentMax 的更新
                if(nums[i] > nums[j]){
                    currentMax = Math.max(currentMax, dp[j]);
                }
            }
            //因为currentMax 记录当前位置i之前的最大上升子序列长度,所以+1,加的i这个
            dp[i] = currentMax+1;
            //更新整个问题的最优解,直接可以输出返回
            maxLen = Math.max(maxLen, dp[i]);
        }
        return maxLen;
   }

现在题目要求最长递增子序列的个数,要进行计数当目前最大长度为x有多个的时候,如果在之后出现最大长度为x+1的话,那么解就有多个
我们需要一个数组来记录当前位置i的(以nums[i]结尾的)最长递增子序列的解个数,如何计数?

显然nums[i] 要大于 nums[j]
当位置i是第一次记录的时候,它的计数值count[i]=count[j], 因为第一次记录就相当于nums[j]的最大递增子序列加上nums[i]而已,解个数不变,但长度+1;如果再次计算位置i的计数值的话就要count[i] += count[j]。怎么区分第一计算还是再次计算位置i。

重新观察原本的求最长递增子序列长度的做法,需要对其进行改造,如上代码。
计算当前位置i的最长递增子序列长度是求出i之前的一个max,然后+1,其实我们在做的事是求0-j,以及i这两部分形成的最长递增子序列,随着j的拓展,最终求到j-1。
改造:把这个步骤一步一步地求出来。前提条件始终是nums[i] > nums[j]
1.如果dp[j] + 1 > dp[i]那么就是i第一次被更新,要更新dp[i]。举例子可以发现基本上dp[j] == dp[i],因为j始终从0到j-1,而每个dp又有初识值1,所以dp[j] + 1 > dp[i];
dp[i] == dp[j]而nums[i] > nums[j],显然dp[i]可以更新

回看原来的做法,可以这么理解,currentMax+1就相当于dp[i], 什么时候更新currentMax,当遇到dp[j] >= currentMax的时候,这时候就如同改造后的dp[j] + 1 > dp[i]
2.如果dp[j] + 1 == dp[i],说明之前dp[i]之前已经求过一次了,并且dp[j]的值是有重复的,dp[i]不用更新

此时配合count数组,定义count[i]为以nums[i]结尾的最长递增子序列的组合数量。遇到1情况,count[i] = count[j],前面介绍过这种情况解个数不变,只是长度+1;遇到2情况,count[i] += count[j]

最后遍历一次dp,将dp[i] == 最长递增序列长度的count[i]相加就是答案

class Solution {
    public int findNumberOfLIS(int[] nums) {
        if (nums == null || nums.length == 0){
            return 0;
        }
        int len = nums.length;
        int[] dp = new int[len];
        int[] count = new int[len];
        //初始值为1,因为不管最长递增子序列还是解最差情况都有1种
        Arrays.fill(dp, 1);
        Arrays.fill(count, 1);
        int maxLen = 1;
        for (int i = 1; i < len; i++) {
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j]) {
                    if (dp[j] + 1 > dp[i]) {
                        dp[i] = dp[j] + 1;
                        //解个数不变,只是长度+1
                        count[i] = count[j];
                    } else if (dp[j] + 1 == dp[i]) {
                        count[i] += count[j];
                    }
                }
            }
            maxLen = Math.max(maxLen, dp[i]);
        }
        int res = 0;
        for (int i = 0; i < len; i++) {
            if (dp[i] == maxLen){
                res += count[i];
            } 
        }
        return res;
    }
}

以题目示例1为例,[1,3,5,4,7]
5和4的dp值都是3,按照新的做法可以看到,求到7的时候,dp[4] = dp[2] +1 = 4,也就是在5的时候是第一次更新7的dp值,到4的时候,因为5和4的dp值是相同的,所以此时7的dp值已经== dp[3] + 1,能遇到这种情况也就说明了之前有相同的dp值,就是5和4的dp值,有重复即有更多解

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值