线性DP

  线性DP是动态规划的基础款,核心特点就是状态线性推进,简单说,我们定义的dp数组,要么是一维、要么是二维,推导顺序要么从左到右、要么从上到下,不会乱跳,比如一维dp[i],只依赖dp[i-1]、dp[i-2]这些前面的状态;二维dp[i][j],也只靠dp[i-1][j]、dp[i][j-1]这些相邻状态,逻辑非常清晰!

一、线性DP思考的四个步骤

1、状态表示:定义dp数组的含义(DP的灵魂!)比如dp[i]表示“到第i个位置的最优解/方案数”,一定要说清楚“i是什么+dp[i]代表什么”。
2、状态转移方程:推导当前状态怎么来的——比如dp[i] = dp[i-1] + dp[i-2],就是“当前状态等于前两个状态的和”,这一步多练两道题就有感觉。
3、初始化:给dp数组设初始值,避免计算出错。比如台阶问题里dp[0] = 1(站在底部不动也是一种方式)。
4、填表顺序:保证计算当前状态时,它依赖的前一个状态已经算完(比如一维从左到右,二维从上到下)。

二、经典例题

1、台阶问题
题目:n级台阶,每次能迈1~k级,求到第n级有多少种方式?
状态表示:dp[i] = 到达第i级台阶的方式数。
转移方程:dp[i] = dp[i-1] + dp[i-2] + ... + dp[i-k](最后一步可以迈1~k级)。
初始化:dp[0] = 1(起点默认1种方式)。
小技巧:可以用前缀和优化,把时间复杂度从O(nk)降到O(n)。

#include <iostream>
#include <vector>
using namespace std;
int main() { 
    int n, k;
    cin >> n >> k;
    vector<int> dp(n + 1, 0);
    dp[0] = 1; // 初始化:起点1种方式
    for (int i = 1; i <= n; i++) {
        // 计算dp[i] = dp[i-1] + ... + dp[i-k](注意边界)
        for (int j = 1; j <= k && i - j >= 0; j++) {
            dp[i] += dp[i - j];
        }
    }
    cout << "到第" << n << "级台阶的方式数:" << dp[n] << endl;
    return 0;
}
// 前缀和优化版(可选,效率更高)
/*
int main() {
    int n, k;
    cin >> n >> k;
    vector<int> dp(n + 1, 0);
    vector<int> pre(n + 1, 0);
    dp[0] = 1;
    pre[0] = 1;
    for (int i = 1; i <= n; i++) {
        // 前缀和计算,避免重复累加
        int l = max(0, i - k);
        dp[i] = pre[i - 1] - (l > 0 ? pre[l - 1] : 0);
        pre[i] = pre[i - 1] + dp[i];
    }
    cout << dp[n] << endl;
    return 0;
}
*/

2、最大子段和(一维最值)
题目:给定整数数组,求连续子数组的最大和。
状态表示:dp[i] = 以第i个元素结尾的最大子段和。
转移方程:dp[i] = max(dp[i-1] + nums[i], nums[i])(要么接前面,要么重新开始)。
初始化:dp[0] = nums[0](第一个元素自身就是最大子段和)。
小技巧:空间可优化到O(1),只用一个变量存前一个状态,不用开数组。

#include <bits/stdc++.h>
using namespace std;
int main() {
    vector<int> nums = {-2, 1, -3, 4, -1, 2, 1, -5, 4}; // 示例数组
    int n = nums.size();
    if (n == 0) return 0;
    // 基础版(开数组,易理解)
    vector<int> dp(n, 0);
    dp[0] = nums[0];
    int max_sum = dp[0];
    for (int i = 1; i < n; i++) {
        dp[i] = max(dp[i - 1] + nums[i], nums[i]);
        max_sum = max(max_sum, dp[i]);
    } 
   // 空间优化版(O(1),推荐)
   /*
    int pre = nums[0]; // 存前一个状态dp[i-1]
    int max_sum = pre;
    for (int i = 1; i < n; i++) {
        pre = max(pre + nums[i], nums[i]);
        max_sum = max(max_sum, pre);
    }
    */
    cout << "连续子数组的最大和:" << max_sum << endl;
    return 0;
}
// 运行结果:6(对应子数组[4,-1,2,1])

3、最长上升子序列
题目:求数组中最长的递增子序列长度(比如[2,7,3,1,9],答案是3)。
状态表示:dp[i] = 以第i个元素结尾的最长上升子序列长度。
转移方程:dp[i] = max(dp[j]+1)(j<i且nums[j]<nums[i])。
初始化:dp[i] = 1(每个元素自身都是长度为1的子序列)。

#include <bits/stdc++.h>
using namespace std;
int main() {
    vector<int> nums = {2, 7, 3, 1, 9}; // 示例数组
    int n = nums.size();
    vector<int> dp(n, 1); // 初始化,每个元素自身长度为1
    int max_len = 1; // 记录最长长度
    for (int i = 1; i < n; i++) {
        // 遍历i前面所有元素,找比nums[i]小的,更新dp[i]
        for (int j = 0; j < i; j++) {
            if (nums[j] < nums[i]) {
                dp[i] = max(dp[i], dp[j] + 1);
            }
        }
        max_len = max(max_len, dp[i]);
    }
    cout << "最长上升子序列长度:" << max_len << endl;
    return 0;
}
// 运行结果:3(对应子序列[2,3,9]或[2,7,9])
posted @ 2026-05-02 23:24  gdyyx  阅读(3)  评论(0)    收藏  举报