1.题目链接:
2.题目描述:
给定一个未排序的整数数组 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。
3. 解法(动态规划):
算法思路:
1. 状态表示:
先尝试定义一个状态:以 i 为结尾的最长递增子序列的「个数」。那么问题就来了,我都不知道以 i 为结尾的最长递增子序列的「长度」是多少,我怎么知道最长递增子序列的个数呢? 因此,我们解决这个问题需要两个状态,一个是「长度」,一个是「个数」:
len[i] 表示:以 i 为结尾的最长递增子序列的长度;
count[i] 表示:以 i 为结尾的最长递增子序列的个数。
2. 状态转移方程:
求个数之前,我们得先知道长度,因此先看 len[i] :
i. | 在求 i 结尾的最长递增序列的长度时,我们已经知道 [0, i - 1] 区间上的 len[j] |
信息,用 j 表示 [0, i - 1] 区间上的下标;
ii. 我们需要的是递增序列,因此 [0, i - 1] 区间上的 nums[j] 只要能和 nums[i] 构成上升序列,那么就可以更新 dp[i] 的值,此时最长长度为 dp[j] + 1 ; iii. 我们要的是 [0, i - 1] 区间上所有情况下的最大值。
综上所述,对于 len[i] ,我们可以得到状态转移方程为:
len[i] = max(len[j] + 1, len[i]) ,其中 0 <= j < i ,并且 nums[j] < nums[i] 。
在知道每一个位置结尾的最长递增子序列的长度时,我们来看看能否得到 count[i] :
i. | 我们此时已经知道 len[i] 的信息,还知道 [0, i - 1] 区间上的 count[j] 信 |
息,用 j 表示 [0, i - 1] 区间上的下标;
ii. 我们可以再遍历一遍 [0, i - 1] 区间上的所有元素,只要能够构成上升序列,并且上 升序列的长度等于 dp[i] ,那么我们就把 count[i] 加上 count[j] 的值。这样循 环一遍之后,count[i] 存的就是我们想要的值。
综上所述,对于 count[i] ,我们可以得到状态转移方程为:
count[i] += count[j] ,其中 0 <= j < i ,并且 nums[j] < nums[i] &&
dp[j] + 1 == dp[i] 。 |
3. 初始化:
对于 len[i] ,所有元素自己就能构成一个上升序列,直接全部初始化为 1 ; | |
◦ | |
◦ |
加进去,因此,我们可以先初始化为 0 ,然后在累加的时候判断一下即可。具体操作情况看代码~
4. 填表顺序:
毫无疑问是「从左往右」。
5. 返回值:
用 manLen 表示最终的最长递增子序列的长度。
根据题目要求,我们应该返回所有长度等于 maxLen 的子序列的个数。
Java算法代码:
class Solution {
public int findNumberOfLIS(int[] nums) {
// 1.创建dp表
// 2.初始化
// 3.填表
// 4.返回值
int n = nums.length;
int[] len = new int[n];
int[] count = new int[n];
for(int i = 0; i < n; i++) len[i] = count[i] = 1;
int retlen = 1, retcount = 1;
for(int i = 1; i < n; i++){
for(int j = 0; j < i; j++){
if(nums[j] < nums[i]){
if(len[j] + 1 == len[i]) count[i] += count[j];
else if(len[j] + 1 > len[i]){
len[i] = len[j] + 1;
count[i] = count[j];
}
}
}
if(retlen == len[i]) retcount += count[i];
else if(retlen < len[i]){
retlen = len[i];
retcount = count[i];
}
}
return retcount;
}
}
运行结果:
动态规划: