子序列问题不像子数组那样要求连续,所以我们选数时得带着'往回看'的思维。LIS(最长递增子序列)是最经典的模板,很多变种都是由它衍生出来的。状态定义往往是 dp[i] 表示以 i 位置结尾的最优解;转移的时候因为允许跳过元素,通常需要一个双层循环 for i { for j < i }。但如果遇到一个数确定不了规律(比如等差、斐波那契),就可以直接升维到 dp[i][j] 表示以 i 和 j 结尾的状态。
最长递增子序列 (LIS)
题目链接:300. 最长递增子序列,找最长严格递增子序列的长度,比如 [10,9,2,5,3,7,101,18] 答案是 4([2,3,7,101])。
dp[i] 的含义就是必须以 nums[i] 结尾的最长递增子序列长度。怎么算?对于每个 i,往前扫一遍 j,只要 nums[j] < nums[i],就可以接在 j 后面,我们就拿 dp[j] + 1 去更新 dp[i]。初始值都是 1,因为单独一个元素也算。
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
vector<int> dp(n, 1);
int ret = 1;
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
dp[i] = max(dp[i], dp[j] + 1);
}
}
ret = max(ret, dp[i]);
}
return ret;
}
};
这道题有更高效的 O(n log n) 贪心+二分法,但那个留到贪心专题再聊;先用 DP 把模型搞透,后面很多变种还要靠它。


