动态规划填表技巧:固定最后一个数 vs 固定倒数第二个数

在动态规划中,填表时固定最后一个数还是倒数第二个数,取决于问题的定义和状态转移方程的设计。


目录

[1. 固定最后一个数](#1. 固定最后一个数)

适用场景

特点

示例

[2. 固定倒数第二个数](#2. 固定倒数第二个数)

适用场景

特点

示例

[3. 固定最后一个数与倒数第二个数的对比](#3. 固定最后一个数与倒数第二个数的对比)

[4. 总结](#4. 总结)


1. 固定最后一个数

适用场景

  • 当问题的解与以某个位置为结尾的子问题相关时,通常固定最后一个数。

  • 例如:最大子数组和、最长递增子序列(LIS)等问题。

特点

  • 状态定义通常为 dp[i],表示以第 i 个元素为结尾的最优解。

  • 状态转移方程通常依赖于前一个状态(dp[i-1])或前面所有状态。

示例

  • 最大子数组和

    状态定义:dp[i] 表示以 nums[i] 为结尾的最大子数组和。

    状态转移:dp[i] = max(nums[i], dp[i-1] + nums[i])

    代码:

    cpp 复制代码
    int maxSubArray(vector<int>& nums) {
        int dp = nums[0], res = dp;
        for (int i = 1; i < nums.size(); i++) {
            dp = max(nums[i], dp + nums[i]);
            res = max(res, dp);
        }
        return res;
    }
  • 最长递增子序列(LIS)

    状态定义:dp[i] 表示以 nums[i] 为结尾的最长递增子序列长度。

    状态转移:dp[i] = max(dp[i], dp[j] + 1),其中 j < inums[j] < nums[i]

    代码:

    cpp 复制代码
    int lengthOfLIS(vector<int>& nums) {
        vector<int> dp(nums.size(), 1);
        int res = 1;
        for (int i = 1; i < nums.size(); i++) {
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j]) {
                    dp[i] = max(dp[i], dp[j] + 1);
                }
            }
            res = max(res, dp[i]);
        }
        return res;
    }

2. 固定倒数第二个数

适用场景

  • 当问题的解与子问题的中间状态相关时,通常固定倒数第二个数。

  • 例如:最长公共子序列(LCS)、编辑距离等问题。

特点

  • 状态定义通常为 dp[i][j],表示前 i 个元素和前 j 个元素之间的某种关系。

  • 状态转移方程通常依赖于 dp[i-1][j-1]dp[i-1][j]dp[i][j-1]

示例

  • 最长公共子序列(LCS)

    状态定义:dp[i][j] 表示字符串 A 的前 i 个字符和字符串 B 的前 j 个字符的最长公共子序列长度。

    状态转移:

    cpp 复制代码
    if (A[i-1] == B[j-1]) {
        dp[i][j] = dp[i-1][j-1] + 1;
    } else {
        dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
    }

    代码:

    cpp 复制代码
    int longestCommonSubsequence(string text1, string text2) {
        int m = text1.size(), n = text2.size();
        vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (text1[i-1] == text2[j-1]) {
                    dp[i][j] = dp[i-1][j-1] + 1;
                } else {
                    dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
                }
            }
        }
        return dp[m][n];
    }
  • 编辑距离

    状态定义:dp[i][j] 表示将字符串 A 的前 i 个字符转换为字符串 B 的前 j 个字符所需的最小操作次数。

    状态转移:

    cpp 复制代码
    if (A[i-1] == B[j-1]) {
        dp[i][j] = dp[i-1][j-1];
    } else {
        dp[i][j] = min({dp[i-1][j], dp[i][j-1], dp[i-1][j-1]}) + 1;
    }

    代码:

    cpp 复制代码
    int minDistance(string word1, string word2) {
        int m = word1.size(), n = word2.size();
        vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
        for (int i = 0; i <= m; i++) dp[i][0] = i;
        for (int j = 0; j <= n; j++) dp[0][j] = j;
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (word1[i-1] == word2[j-1]) {
                    dp[i][j] = dp[i-1][j-1];
                } else {
                    dp[i][j] = min({dp[i-1][j], dp[i][j-1], dp[i-1][j-1]}) + 1;
                }
            }
        }
        return dp[m][n];
    }

3. 固定最后一个数与倒数第二个数的对比

特性 固定最后一个数 固定倒数第二个数
适用问题 子数组、子序列问题 双序列、矩阵类问题
状态定义 dp[i]dp[i][j] dp[i][j]
状态转移 依赖前一个状态或前面所有状态 依赖 dp[i-1][j-1] 等中间状态
典型问题 最大子数组和、LIS LCS、编辑距离

4. 总结

  • 固定最后一个数:适用于子数组或子序列问题,状态转移通常依赖于前一个状态或前面所有状态。

  • 固定倒数第二个数 :适用于双序列或矩阵类问题,状态转移通常依赖于中间状态(如 dp[i-1][j-1])。

相关推荐
gb4215287几秒前
负载均衡算法中的加权随机算法
windows·算法·负载均衡
xdlka1 小时前
C++初学者4——标准数据类型
开发语言·c++·算法
go54631584651 小时前
大规模矩阵构建与高级算法应用
线性代数·算法·矩阵
向左转, 向右走ˉ1 小时前
为什么分类任务偏爱交叉熵?MSE 为何折戟?
人工智能·深度学习·算法·机器学习·分类·数据挖掘
云边有个稻草人2 小时前
【C++】第十九节—一文万字详解 | AVL树实现
数据结构·c++·avl树·avl树的插入·avl树的旋转·avl树实现·avl树的结构
霜绛3 小时前
机器学习笔记(四)——聚类算法KNN、Kmeans、Dbscan
笔记·算法·机器学习·kmeans·聚类
晨非辰3 小时前
#C语言——学习攻略:深挖指针路线(三)--数组与指针的结合、冒泡排序
c语言·开发语言·数据结构·学习·算法·排序算法·visual studio
zzywxc7873 小时前
编程算法在金融、医疗、教育、制造业等领域的落地案例
人工智能·算法·金融·自动化·copilot·ai编程
zzywxc7873 小时前
编程算法在金融、医疗、教育、制造业的落地应用。
人工智能·深度学习·算法·机器学习·金融·架构·开源
conkl4 小时前
构建 P2P 网络与分布式下载系统:从底层原理到安装和功能实现
linux·运维·网络·分布式·网络协议·算法·p2p