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

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

给定一个未排序的整数数组 nums返回最长递增子序列的个数

注意 这个数列必须是 严格 递增的。

示例

  • 示例 1:
1
2
输入: [1,3,5,4,7]
输出: 2

解释: 有两个最长递增子序列,分别是 [1, 3, 4, 7] 和[1, 3, 5, 7]。

  • 示例 2:
1
2
输入: [2,2,2,2,2]
输出: 5

解释: 最长递增子序列的长度是1,并且存在5个子序列的长度为1,因此输出5。

思路

基本思想

300题 的思路类似,300题求的是最长递增子序列的长度,所以只需要在求以每个元素结尾的递增子序列中统计出长度最长的即可

本题中是求最长递增子序列的个数有多少,有可能两个最长递增子序列是以同一个元素结尾(示例1),所以没办法按照300题的方法进行统计

核心就是将300题中dp数组的更新逻辑拆分成两部分

此时需要新增一个容器,用来存储当前位置之前,有几个最长的子序列,于是本题中就有两个容器需要更新,分别的含义为:

dp[i]代表以第i个元素结尾的最长递增子序列的长度

count[i]代表以第i个元素结尾的最长递增子序列的个数

一旦dp[i]更新,有两种情况:

  1. 找到了更长的递增子序列,此时当前递增子序列的长度需要更新,递增子序列的个数也需要更新,其中dp[j]+1代表第j个元素之前的元素加上第i个元素形成的递增子序列

    1
    2
    3
    4
    5
    6
    
    if(dp[j]+1>dp[i]){
        //长度应该+1
    	dp[i]=dp[j]+1;
        //个数应该一样
    	count[i]=count[j];
    }
    
  2. 找到了一样长的递增子序列,我们认为此时也需要更新,但是count的更新方式不一样了:

    1
    2
    3
    4
    5
    6
    
    if(dp[j]+1==dp[i]){
        //长度不变
    	dp[i]=dp[j]+1;
        //个数累加
    	count[i]+=count[j];
    }
    

以上两种情况需要更新count,其余情况正常按照300题的思路更新dp即可,但是count[i]中记录的是第i个元素之前的最长递增子序列的个数。不知道全局的最长递增子序列的长的是多少,所以需要统计一下所有符合最长递增子序列长度的个数,累加起来,累加的时候也有技巧

当以当前元素结尾的最长递增子序列长度是全局的最长递增子序列长度时,此时以当前元素结尾的最长递增子序列个数也需要统计起来,相当于用dp[i]判断count[i]是否需要被统计:

1
2
3
4
5
6
for(int i=0;i<nums.size();++i){
	//用dp[i]的结果判断count[i]是否需要被统计
	if(max=dp[i]){
		res+=count[i];
	}
}

执行流程

  1. 初始化两个数组,由于单个元素也算递增,所以都初始化为1
  2. 按照300题的逻辑统计dp数组
  3. 将dp数组的更新逻辑拆分成两部分,分别更新count
  4. 更新最长递增子序列的长度
  5. 统计count中那些是最长递增子序列的长度

代码

根据以上分析,得出以下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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 res=0;
        int max_len=0;
        for(int i=1;i<nums.size();++i){
            for(int j=0;j<i;++j){
                //dp[i]可能需要更新
                if(nums[i]>nums[j]){
                    //将dp[i]更新的情况分成两种
                    //原本是dp[i]=max(dp[i],dp[j]+1);
                    if(dp[j]+1>dp[i]){//大于,遇到了更长的递增子序列
                        dp[i]=dp[j]+1;
                        //相等是因为第i个元素比第j个元素大
                        //此时j+1就到了i,所以最长递增子序列的个数应该一样
                        count[i]=count[j];
                    }
                    //只能是else if,否则可能上面的if更新之后,下面的if也更新
                    else if(dp[j]+1==dp[i]){//等于,遇到了一样的递增子序列
                        dp[i]=dp[j]+1;
                        count[i]+=count[j];
                    }
                }
            }
            //记录最长的长度
            max_len=max(max_len,dp[i]);
        }
        //统计最长递增子序列的个数
        for(int i=0;i<nums.size();++i){
            if(max_len==dp[i]){
                res+=count[i];
            }
        }
        return res;
    }
};

总结

将300题中更新dp[i]的代码进行拆分,分成等于和大于,都认为是更新,但是两者更新count的方式不一样。大于时相当于找到了更长的最长递增子序列,此时覆盖即可

等于时相当于找到了一样的最长递增子序列,此时需要累加之前的结果

核心就是将dp数组的更新情况分开成两种

最长递增子序列的长度变长,此时就是在原有的最长递增子序列的基础上增加了一个元素,所以最长递增子序列的个数不变

最长递增子序列的长度不变,此时就是遇到了相同的最长递增子序列的长度,需要累加