在此之前,我们已经讲解了不少动态规划子数组类型的问题,本篇开始将介绍一些子序列类型问题。
首先,我们需要了解什么是子序列。子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序,子序列 包含了 子数组 ,换而言之,对于一个数组,其子数组就是一个子序列,但子序列不都是子数组。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
题目描述
给你一个整数数组 nums ,找到其中最长 严格递增 子序列的长度。
示例:
输入:nums = [10 , 9 , 2 , 5 , 3 , 7 , 91 , 18]
输出:4
解释:最长递增子序列是 [2 , 3 , 7 , 91],因此长度为 4 。
解题思路
1. 状态定义
首先,我们需要明确状态的定义。在这个问题中,我们定义 dp[i]
为以 nums[i]
结尾的最长递增子序列的长度。这个定义很关键,因为它允许我们仅通过查看数组的前缀(即 nums[0]
到 nums[i-1]
)来找出以 nums[i]
结尾的最长递增子序列的长度。
2. 初始化
接下来,我们需要初始化 dp
数组。由于任何单独的元素都可以看作是一个长度为 1 的递增子序列,因此我们将 dp
数组的所有元素初始化为 1。这表示每个元素至少可以构成一个长度为 1 的递增子序列。
3. 状态转移
状态转移是动态规划的核心。对于每个位置 i
(从 1 到 n-1
,其中 n
是数组的长度),我们需要检查它之前的所有位置 j
(从 0 到 i-1
)。如果 nums[i] > nums[j]
,那么我们可以尝试将 nums[i]
添加到以 nums[j]
结尾的递增子序列的末尾,从而形成一个更长的递增子序列。为了找到以 nums[i]
结尾的最长递增子序列,我们需要比较所有可能的 dp[j] + 1
(其中 j < i
且 nums[i] > nums[j]
),并选择其中的最大值来更新 dp[i]
。
但注意,实际上在遍历过程中,我们并不直接计算这个最大值,而是用一个循环来更新 dp[i]
。
4. 边界条件
由于我们是从索引 1 开始计算 dp[i]
的(因为 dp[0]
初始化为 1),所以不需要特别处理边界条件。但是,理解这一点很重要:当我们说“以 nums[i]
结尾的最长递增子序列”时,我们实际上是在考虑所有可能的、以 nums[i]
作为最后一个元素的递增子序列。
5. 最终结果
遍历完整个数组后,dp
数组中的最大值就是我们要找的最长递增子序列的长度。
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
vector<int> dp(n, 1); // dp[i] 以nums[i]为结尾的最长递增子序列长度 每个元素至少可以构成长度为1的递增子序列
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
dp[i] = max(dp[i], dp[j] + 1);
}
}
}
int ret = dp[0];
for (int i = 1; i < n; i++) {
ret = max(ret, dp[i]);
}
return ret;
}
};