leetcode:673. 最长递增子序列的个数

本文介绍了如何使用动态规划解决LeetCode上的673号问题——最长递增子序列的个数。文章详细阐述了状态定义、状态转移、初始化、遍历顺序以及如何返回结果,同时提供了两种不同的代码实现方式。通过对每个状态的分析,解释了如何计算以特定数字结尾的最长递增子序列的个数,并展示了如何逐步求解最长递增子序列的个数。
摘要由CSDN通过智能技术生成

题目来源

题目描述

在这里插入图片描述

题目解析

这是一道典型的序列DP
这道题是leetcode:300. 最长递增子序列的扩展

序列DP

之前leetcode:300. 最长递增子序列相比,本题问的是最长上升子序列的个数。我们只需要在之前LIS问题的基础上通过[记录额外信息]来进行求解即可。

(1)状态定义:

在之前LIS问题中,我们定义 f [ i ] f[i] f[i]为考虑nums[i]为结尾的最长上升子序列的长度。因此答案为所有 f [ 0...... ( n − 1 ) ] f[0......(n-1)] f[0......(n1)]中的最大值。

不失一般性地考虑 d p [ i ] dp[i] dp[i]该如何转移:

  • 由于每个数都能独立成为一个子序列,因此起始必然有 d p [ i ] = i dp[i] = i dp[i]=i
  • 枚举区间 [ 0 , i ) [0, i) [0,i)的所有数 n u m s [ j ] nums[j] nums[j],如果满足 n u m s [ j ] < n u m s [ i ] nums[j] < nums[i] nums[j]<nums[i],说明 n u m s [ i ] nums[i] nums[i]可以接在 n u m s [ i ] nums[i] nums[i]结尾的后面形成上升子序列,此时使用 d p [ j ] dp[j] dp[j]更新dp[i],即有 d p [ i ] = d p [ i ] + 1 dp[i] = dp[i] + 1 dp[i]=dp[i]+1
if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);

即位置i的最长递增子序列长度等于j从0到i-1各个位置的最长子序列+1的最大值。

回到本题,因为我们需要求解的是最长上升子序列的个数,因此需要额外定义 c n t [ i ] cnt[i] cnt[i]考虑以 n u m s [ i ] nums[i] nums[i]结尾的最长上升子序列的个数

小结:

  • dp[i]:到nums[i]为止的最长递增子序列长度
  • cnt[i]:到cnt[i]为止的最长递增子序列个数

(2)状态转移

那么它的状态转移应该怎么转移呢?我们需要考虑两个维度。一个是 d p [ i ] dp[i] dp[i]的更新,一个是 c n t [ i ] cnt[i] cnt[i]的更新。

那么如何更新 c n t [ i ] cnt[i] cnt[i]呢?

  • n u m s [ i ] nums[i] nums[i]结尾的数字,最长递增子序列的个数是cnt[i]
  • 那么在 n u m s [ i > n u m s [ j ] ] nums[i > nums[j]] nums[i>nums[j]]的前提下,如果在 [ 0 , i − 1 ] [0, i-1] [0,i1]的范围内,找到了 j j j,使得 d p [ j ] + 1 > d p [ i ] dp[j] + 1 > dp[i] dp[j]+1>dp[i],说明找到了一个更长的以 n u m s [ i ] nums[i] nums[i]结尾的递增子序列,此时 n u m s [ i ] nums[i] nums[i]要追加到之前 [ . . . . . n u m s [ j ] ] [.....nums[j]] [.....nums[j]]的后面,形成 [ . . . . . . n u m s [ j ] , n u m s [ i ] [......nums[j], nums[i] [......nums[j],nums[i]
  • 那么以 n u m s [ j ] nums[j] nums[j]为结尾的最长递增子序列的个数,就是最新的以nums[i]为结尾的子串的最长递归子序列的个数,即 c n t [ j ] = c n t [ i ] cnt[j] = cnt[i] cnt[j]=cnt[i]
  • n u m s [ i ] > n u m s [ j ] ] nums[i] > nums[j]] nums[i]>nums[j]]的前提下,如果在 [ 0 , i − 1 ] [0, i-1] [0,i1]的范围内,找到了 j j j,使得 d p [ j ] + 1 = d p [ i ] dp[j] + 1 = dp[i] dp[j]+1=dp[i],说明找到了两个相同长度的递增子序列,那么以nums[i] 为结尾的子串的最长递增子序列的个数 就应该加上以nums[j]]为结尾的子串的最长递增子序列的个数,即: c n t [ i ] + = c n t [ j ] ; cnt[i] += cnt[j]; cnt[i]+=cnt[j];

小结, c n t [ i ] cnt[i] cnt[i]该如何转移:

  • 由于每个数都能独自一个称为子序列,因此起始必然有 c n t [ i ] = 1 cnt[i] = 1 cnt[i]=1
  • 枚举区间 [ 0 , i ) [0, i) [0,i)的所有数 n u m s [ j ] nums[j] nums[j],如果满足 n u m s [ j ] < n u m s [ i ] nums[j] < nums[i] nums[j]<nums[i],说明 n u m s [ i ] nums[i] nums[i]可以接在 n u m s [ i ] nums[i] nums[i]结尾的后面形成上升子序列,这时候对 d p [ i ] dp[i] dp[i] d p [ i + 1 ] dp[i + 1] dp[i+1]的大小关系分情况讨论:
    • 满足 d p [ i ] < d p [ i ] + 1 dp[i] < dp[i] + 1 dp[i]<dp[i]+1:说明最长递增子序列的长度增加了,dp[i] = dp[j] + 1,长度增加,数量不变 cnt[i] = cnt[j]
    • 满足 d p [ i ] = d p [ i ] + 1 dp[i] = dp[i] + 1 dp[i]=dp[i]+1:说明最长递增子序列的长度并没有增加,但是出现了长度一样的情况,数量增加 cnt[i] += cnt[j]
if (nums[i] > nums[j]) {
    if (dp[j] + 1 > dp[i]) {
        cnt[i] = cnt[j];
    } else if (dp[j] + 1 == dp[i]) {
        cnt[i] += cnt[j];
    }
    dp[i] = max(dp[i], dp[j] + 1);
}

当然也可以这么写:

if (nums[i] > nums[j]) {
    if (dp[j] + 1 > dp[i]) {
        dp[i] = dp[j] + 1; // 更新dp[i]放在这里,就不用max了
        cnt[i] = cnt[j];
    } else if (dp[j] + 1 == dp[i]) {
        cnt[i] += cnt[j];
    }
}

(3)初始化:

  • dp[i]记录了i之前(包括i)最长递增序列的长度。最小的长度也是1,所以dp[i]初始为1。
  • cnt[i]记录了以nums[i]为结尾的字符串,最长递增子序列的个数。那么最少也就是1个,所以cnt[i]初始为1。
vector<int> dp(nums.size(), 1);
vector<int> count(nums.size(), 1);

(4)遍历顺序:

  • dp[i] 是由0到i-1各个位置的最长升序子序列 推导而来,那么遍历i一定是从前向后遍历。
  • j其实就是0到i-1,遍历i的循环里外层,遍历j则在内层

(5)返回结果

  • 题目要求最长递增序列的长度的个数,我们应该把最长长度记录下来
  • 然后遍历dp数组,如果dp数组记录的最大长度dp[i]等于max_length,将对应的数量count[i]加到结果res中

代码如下:

class Solution {
public:
    int findNumberOfLIS(vector<int>& nums) {
        if (nums.size() <= 1) return nums.size();
        vector<int> dp(nums.size(), 1);
        vector<int> count(nums.size(), 1);
        int maxCount = 0;
        for (int i = 1; i < nums.size(); i++) {
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j]) {
                    if (dp[j] + 1 > dp[i]) {
                        dp[i] = dp[j] + 1;
                        count[i] = count[j];
                    } else if (dp[j] + 1 == dp[i]) {
                        count[i] += count[j];
                    }
                }
                if (dp[i] > maxCount) maxCount = dp[i];
            }
        }
        int result = 0;
        for (int i = 0; i < nums.size(); i++) {
            if (maxCount == dp[i]) result += count[i];
        }
        return result;
    }
};

本题通过存储pair类型的数组,pair的first存储当前位置的最长长度,pair的second存储达到这个最长长度的个数。

class Solution {
public:
    int findNumberOfLIS(vector<int>& nums) {
        int size = nums.size();

        vector<pair<int, int>> dp(size, {0,0});
        int max_value = 1;
        int cnt = 0;
        for(int i = 0; i < size; i++){
            dp[i] = {1,1};
            for(int j = 0; j < i; j++){
                if(nums[j] < nums[i]){

                   //相等说明达到这个长度还有不同路径实现
                    if(dp[i].first == dp[j].first + 1){
                        dp[i].second += dp[j].second;
                    }
                    //不相等则说明需要重新更新最大长度,此时的条数等于前一个状态的条数,因为达到前一个状态可以有前一个状态的条数,达到此状态条数和原来的条数在更新的时候是相等的。
                    if(dp[i].first < dp[j].first + 1){
                        dp[i].second = dp[j].second;
                    } 
                    dp[i].first = max(dp[i].first, dp[j].first + 1);  
                    max_value = max(dp[i].first, max_value);           
                }
            }
        }
        for(int i = 0; i < size; i ++){
            if(dp[i].first == max_value){
                cnt += dp[i].second;
            }
        }
        return cnt;
        
    }
};

思路二

小结

求最长连续递增序列

给定一个为排序的整数数组,找到最长而且连续的递增序列

  • dp[i]表示表示前i个序列(以nums[i]结尾的)最长连续递增序列的长度
  • 只需要关注前一个位置 i i i与前一个位置 i − 1 i - 1 i1的值的大小
    • n u m s [ i ] > n u m s [ i + 1 ] nums[i] > nums[i + 1] nums[i]>nums[i+1]时, i i i至少可以与 i − 1 i - 1 i1形成一个连续递增序列,因为它们两是挨着的并且递增的,因此 d p [ i ] = d p [ i − 1 ] + 1 dp[i] = dp[i - 1] + 1 dp[i]=dp[i1]+1
    • n u m s [ i ] < = n u m s [ i + 1 ] nums[i] <= nums[i + 1] nums[i]<=nums[i+1]时,不能形成连续递增序列,所以dp[i]与dp[i-1]无关

可以看到,dp[i]的状态只依赖前一个状态

class Solution {


public:
    int lengthOfLIS(vector<int>& nums) {
        if(nums.empty()){
            return 0;
        }

        //用来存储预期的结果
        std::vector<int> dp(nums.size(), 1);//至少可以与其自身形成递增序列
        int max = 1;
        for (int i = 1; i < nums.size(); ++i) {
            if(nums[i] > nums[i - 1]){
                dp[i] = dp[i - 1] + 1;
                max = std::max(max, dp[i]);
            }
        }

        return max;
    }
};

求最长连续递增子序列

  • dp[i]表示前i个序列(以nums[i]结尾的)最长连续递增子序列的长度
  • dp[i]的状态可能从dp[0…i-1]之前的状态转移而来,也就是需要关注num[0…i-1]与nums[i]的关系

参考

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值