二维动态规划状态压缩的一些思考

问题

对于以下一段代码:代码来源:labuladong

int longestPalindromeSubseq(string s) {
    int n = s.size();
    // dp 数组全部初始化为 0
    vector<vector<int>> dp(n, vector<int>(n, 0));
    // base case
    for (int i = 0; i < n; i++)
        dp[i][i] = 1;
    // 反着遍历保证正确的状态转移
    for (int i = n - 2; i >= 0; i--) {
        for (int j = i + 1; j < n; j++) {
            // 状态转移方程
            if (s[i] == s[j])
                dp[i][j] = dp[i + 1][j - 1] + 2;
            else
                dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
        }
    }
    // 整个 s 的最长回文子串长度
    return dp[0][n - 1];
}

经过状态压缩可以变为:

for (int i = n - 2; i >= 0; i--) {
    // 存储 dp[i+1][j-1] 的变量
    int pre = 0;
    for (int j = i + 1; j < n; j++) {
        int temp = dp[j];
        if (s[i] == s[j])
            // dp[i][j] = dp[i+1][j-1] + 2;
            dp[j] = pre + 2;
        else
            dp[j] = max(dp[j], dp[j - 1]);
        // 到下一轮循环,pre 就是 dp[i+1][j-1] 了
        pre = temp;
    }
}

分析

下面将围绕上述代码块进行分析。

为什么可以这么做?我想探讨一下其背后的思想。
个人认为,最主要的原因在于:
抓住循环运行的顺序,将外层循环变量i的职责,交给时间差来完成

如何理解这句话?
假设动态规划的过程是
在这里插入图片描述

上述代码的运行过程为:内层j从小到大,外层i从大到小的顺序运行。
那么,如果我们抛去外层循环i,把i当成一个时刻。
此刻运行到了时刻i,根据循环的顺序,上一个时刻就是i+1
那么,dp[i][j] = function(dp[i+1][j]) 就等同于 dp[j] = function(dp[j])
而对于 j - 1j - 2 等等,由于我们此刻的操作并不会进行覆盖,那么它的值是此刻还是以前都不影响。
所以

dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);

等同于

dp[j] = max(dp[j], dp[j - 1]);

便可以理解了。

dp[i][j] = dp[i + 1][j - 1] + 2;

等同于

dp[j] = pre + 2;

要如何理解呢?

原因就在于,当进行到此刻i 时(铭记,i我们已经当成了时间概念),
dp[j-1]已经先于dp[j]被更新,这就意味着,dp[j-1]此刻即为dp[i][j-1],而上一时刻的值:dp[i+1][j-1] 已经被无情地覆盖了。但我们在更新dp[i][j]时却要用到它,那么该怎么办呢?
这就要求我们,每次更新dp[j]时,将它更新之前的值记录下来,留给下一个j使用。

比如,时刻i = 3, 我们准备更新j = 4,此时,j = 4位置的值实际为 dp[4][4] ,(上一时刻i + 1
我们保存下来到pre 之后,更新j = 4,此时该位置的值为 dp[3][4]
当我们到达 i = 3j = 5 时,就能够拿到pre的值 dp [4][4],即dp [i + 1][j-1]

按照上述说法,每次j循环一开始,更新前,就要记录下来该位置的旧值。所以temp变量在内循环的第一句就出现,就是这个原因。

for (int i = n - 2; i >= 0; i--) {
    // 存储 dp[i+1][j-1] 的变量
    int pre = 0;
    for (int j = i + 1; j < n; j++) {
        int temp = dp[j];
        ...
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值