leetcode-master/problems/0673.最长递增子序列的个数.md

367 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<p align="center">
<a href="https://programmercarl.com/other/xunlianying.html" target="_blank">
<img src="../pics/训练营.png" width="1000"/>
</a>
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 673.最长递增子序列的个数
[力扣题目链接](https://leetcode.cn/problems/number-of-longest-increasing-subsequence/)
给定一个未排序的整数数组,找到最长递增子序列的个数。
示例 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.最长上升子序列](https://programmercarl.com/0300.最长上升子序列.html) 的进阶版本
1. 确定dp数组dp table以及下标的含义
这道题目我们要一起维护两个数组。
dp[i]i之前包括i最长递增子序列的长度为dp[i]
count[i]以nums[i]为结尾的字符串最长递增子序列的个数为count[i]
2. 确定递推公式
在300.最长上升子序列 中,我们给出的状态转移是:
if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
位置i的最长递增子序列长度 等于j从0到i-1各个位置的最长升序子序列 + 1的最大值。
本题就没那么简单了我们要考虑两个维度一个是dp[i]的更新一个是count[i]的更新。
那么如何更新count[i]呢?
以nums[i]为结尾的字符串最长递增子序列的个数为count[i]。
那么在nums[i] > nums[j]前提下,如果在[0, i-1]的范围内找到了j使得dp[j] + 1 > dp[i],说明找到了一个更长的递增子序列。
那么以j为结尾的子串的最长递增子序列的个数就是最新的以i为结尾的子串的最长递增子序列的个数count[i] = count[j]。
在nums[i] > nums[j]前提下,如果在[0, i-1]的范围内找到了j使得dp[j] + 1 == dp[i],说明找到了两个相同长度的递增子序列。
那么以i为结尾的子串的最长递增子序列的个数 就应该加上以j为结尾的子串的最长递增子序列的个数count[i] += count[j];
代码如下:
```CPP
if (nums[i] > nums[j]) {
if (dp[j] + 1 > dp[i]) {
count[i] = count[j];
} else if (dp[j] + 1 == dp[i]) {
count[i] += count[j];
}
dp[i] = max(dp[i], dp[j] + 1);
}
```
当然也可以这么写:
```CPP
if (nums[i] > nums[j]) {
if (dp[j] + 1 > dp[i]) {
dp[i] = dp[j] + 1; // 更新dp[i]放在这里就不用max了
count[i] = count[j];
} else if (dp[j] + 1 == dp[i]) {
count[i] += count[j];
}
}
```
这里count[i]记录了以nums[i]为结尾的字符串最长递增子序列的个数。dp[i]记录了i之前包括i最长递增序列的长度。
题目要求最长递增序列的长度的个数,我们应该把最长长度记录下来。
代码如下:
```CPP
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]) {
count[i] = count[j];
} else if (dp[j] + 1 == dp[i]) {
count[i] += count[j];
}
dp[i] = max(dp[i], dp[j] + 1);
}
if (dp[i] > maxCount) maxCount = dp[i]; // 记录最长长度
}
}
```
3. dp数组如何初始化
再回顾一下dp[i]和count[i]的定义
count[i]记录了以nums[i]为结尾的字符串,最长递增子序列的个数。
那么最少也就是1个所以count[i]初始为1。
dp[i]记录了i之前包括i最长递增序列的长度。
最小的长度也是1所以dp[i]初始为1。
代码如下:
```
vector<int> dp(nums.size(), 1);
vector<int> count(nums.size(), 1);
```
**其实动规的题目中初始化很有讲究也很考察对dp数组定义的理解**
4. 确定遍历顺序
dp[i] 是由0到i-1各个位置的最长升序子序列 推导而来那么遍历i一定是从前向后遍历。
j其实就是0到i-1遍历i的循环里外层遍历j则在内层代码如下
```CPP
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]) {
count[i] = count[j];
} else if (dp[j] + 1 == dp[i]) {
count[i] += count[j];
}
dp[i] = max(dp[i], dp[j] + 1);
}
if (dp[i] > maxCount) maxCount = dp[i];
}
}
```
最后还有再遍历一遍dp[i]把最长递增序列长度对应的count[i]累计下来就是结果了。
代码如下:
```CPP
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]) {
count[i] = count[j];
} else if (dp[j] + 1 == dp[i]) {
count[i] += count[j];
}
dp[i] = max(dp[i], dp[j] + 1);
}
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];
}
```
统计结果可能有的同学又有点看懵了那么就再回顾一下dp[i]和count[i]的定义。
5. 举例推导dp数组
输入:[1,3,5,4,7]
![673.最长递增子序列的个数](https://code-thinking-1253855093.file.myqcloud.com/pics/20230310000656.png)
**如果代码写出来了怎么改都通过不了那么把dp和count打印出来看看对不对**
以上分析完毕C++整体代码如下:
```CPP
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;
}
};
```
* 时间复杂度O(n^2)
* 空间复杂度O(n)
还有O(nlog n)的解法使用树状数组今天有点忙就先不写了感兴趣的同学可以自行学习一下这里有我之前写的树状数组系列博客https://blog.csdn.net/youngyangyang04/category_871105.html (十年前的陈年老文了)
# 其他语言版本
## Java
```java
class Solution {
public int findNumberOfLIS(int[] nums) {
if (nums.length <= 1) return nums.length;
int[] dp = new int[nums.length];
for(int i = 0; i < dp.length; i++) dp[i] = 1;
int[] count = new int[nums.length];
for(int i = 0; i < count.length; i++) count[i] = 1;
int maxCount = 0;
for (int i = 1; i < nums.length; 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.length; i++) {
if (maxCount == dp[i]) result += count[i];
}
return result;
}
}
```
## Python
```python
class Solution:
def findNumberOfLIS(self, nums: List[int]) -> int:
size = len(nums)
if size<= 1: return size
dp = [1 for i in range(size)]
count = [1 for i in range(size)]
maxCount = 0
for i in range(1, size):
for j in range(i):
if nums[i] > nums[j]:
if dp[j] + 1 > dp[i] :
dp[i] = dp[j] + 1
count[i] = count[j]
elif dp[j] + 1 == dp[i] :
count[i] += count[j]
if dp[i] > maxCount:
maxCount = dp[i];
result = 0
for i in range(size):
if maxCount == dp[i]:
result += count[i]
return result;
```
## Go
```go
func findNumberOfLIS(nums []int) int {
size := len(nums)
if size <= 1 {
return size
}
dp := make([]int, size);
for i, _ := range dp {
dp[i] = 1
}
count := make([]int, size);
for i, _ := range count {
count[i] = 1
}
maxCount := 0
for i := 1; i < size; i++ {
for 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]
}
}
}
result := 0
for i := 0; i < size; i++ {
if maxCount == dp[i] {
result += count[i]
}
}
return result
}
```
## JavaScript
```js
var findNumberOfLIS = function(nums) {
const len = nums.length;
if(len <= 1) return len;
let dp = new Array(len).fill(1); // i之前包括i最长递增子序列的长度为dp[i]
let count = new Array(len).fill(1); // 以nums[i]为结尾的字符串最长递增子序列的个数为count[i]
let res = 0;
for(let i = 1; i < len; i++){
for(let j = 0; j < i; j++){
if(nums[i] > nums[j]){
if(dp[j] + 1 > dp[i]){ // 第 j 个数字为前一个数字的子序列是否更更长
dp[i] = dp[j] + 1; //更新 dp[i]
count[i] = count[j]; // 重置count[i]
} else if(dp[j] + 1 === dp[i]){ // 和原来一样长
count[i] += count[j]; //更新count[i]
}
}
}
}
let max = Math.max(...dp); //扩展运算符找到最大长度
for(let i = 0; i < len; i++) if(dp[i] === max) res += count[i]; // 累加
return res;
};
```
<p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
</a>