给定一个未排序的整数数组 nums
, 返回最长递增子序列的个数 。
注意 这个数列必须是 严格 递增的。
示例 1:
输入: [1,3,5,4,7] 输出: 2 解释: 有两个最长递增子序列,分别是 [1, 3, 4, 7] 和[1, 3, 5, 7]。
示例 2:
输入: [2,2,2,2,2] 输出: 5 解释: 最长递增子序列的长度是1,并且存在5个子序列的长度为1,因此输出5。
做这题之前最好先做一下力扣的300. 最长递增子序列 ,理解了求最长递增子序列才能更好的写出这题以及理解我本篇的思路,思路以及代码在我的上一篇动态规划LeetCode-300.最长递增子序列-优快云博客
遇到最值问题,我们可以往动态规划方法去想一想,此题可以用动态规划来进行解题,动规五部曲(dp含义、递推公式、初始化、遍历顺序、打印数组) ,主要的就是把大问题拆解成小问题,小问题的值保存下来,从小问题的答案推导到最终答案,空间换时间。
题目及思路:这题的思路和求最长递增子序列的思路基本一样,之前我们只求最长递增子序列的长度,但在得到最长子序列长度的过程中是有不同的路径组成的。就比如300. 最长递增子序列的示例1,dp[i]存的是以nums[i]为结尾的最长子序列长度,示例1最长长度是dp[6]=4,而得到长度4的子序列有两种不同路径(2,3,7,101)和(2,5,7,101)。所以到现在就可以得出这题于300的本质区别就是求出通往最长递增子序列的道路有几条。
力扣的300. 最长递增子序列
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
那所以我们在300题的基础上增添一个数组count[i]即可。
dp[i]的含义:以nums[i]为结尾的最长子序列长度。它通过遍历 j < i 的所有元素,检查是否能将 nums[i] 接在 nums[j] 后面形成更长的子序列,从而更新 dp[i] 的值。
count[i]的含义:以nums[i]为结尾、长度为dp[i]的最长递增子序列的个数。它的更新逻辑与 dp[i] 同步: if当发现更长的子序列(dp[j] + 1 > dp[i])时,count[i] 需要继承 count[j] 的值,因为这是当前找到的最优路径。
else if当遇到相同长度的子序列(dp[j] + 1 == dp[i])时,count[i] 需要累加 count[j] 的值,因为存在多条路径可以形成相同长度的子序列。
dp[i]的含义:以nums[i]为结尾的最长子序列长度。
count[i]的含义:以nums[i]为结尾、长度为dp[i]的最长递增子序列的个数。
初始化:一开始nums每个下标i对应数字单独就是子序列也是递增,所以dp[i++]=1;count[i++]=1
递推公式:
遍历顺序:从前往后
打印数组:当遇到疑惑或者提交错误时,打印数组出来比较快速的看看哪一步有错。
以下是我在力扣c语言提交的代码,仅供参考:
int findNumberOfLIS(int* nums, int numsSize) {
if (numsSize == 0) return 0;
// dp[i]表示以nums[i]结尾的最长递增子序列的长度
int dp[numsSize];
// count[i]表示以nums[i]结尾、长度为dp[i]的的最长递增子序列的个数
int count[numsSize];
//max保存最长递增子序列的长度
int max = 1;
//初始化:dp[i]=1默认每个元素的最长递增子序列至少是它自己
//count[i]=1默认每个元素结尾的子序列个数为1
for(int i = 0;i<numsSize;i++)
{
dp[i] = 1;
count[i] = 1;
}
for(int i = 1;i<numsSize;i++)
{
for(int j = 0;j<i;j++)
{
if(nums[i] > nums[j])
{
if(dp[i] < dp[j]+1)
{
dp[i] = dp[j] +1;// 更新长度
count[i] = count[j];// 继承个数
}
else if (dp[i] == dp[j] + 1)
{
count[i] += count[j]; // 增加个数
}
}
// 更新最长长度
if(dp[i] > max) max = dp[i];
}
}
int totalCount = 0;
for(int i = 0;i<numsSize;i++)
{
if(dp[i] == max)
{
// 统计最长序列个数
totalCount+=count[i];
}
}
return totalCount;
}