每天一道LeetCode-----计算两个序列最长的公共子序列长度

本文介绍了如何使用动态规划解决LeetCode中的'计算两个序列最长的重复子序列长度'问题。通过分析序列A和B,转换成动态规划问题,用dp数组表示子序列的最长公共前缀。当A[i]等于B[j]时,dp[i][j]等于dp[i+1][j+1]+1,否则dp[i][j]为0。最后,通过降维优化,将二维dp数组简化为一维。文章详细阐述了降维后的动态规划过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原题链接Maximum Length of Repeated Subarray

这里写图片描述

计算两个序列最长的相同子序列的长度


简单暴力的方法是对A中每个元素遍历一遍序列B,找到相同的位置后计算从这个位置开始有多少个元素和A是相同的,唔…大概是这个样子

class Solution {
public:
    int findLength(vector<int>& A, vector<int>& B) {
        int maxLen = 0;
        for(int i = 0; i < A.size(); ++i)
        {
            for(int j = 0; j < B.size(); ++j)
            {
                if(A[i] != B[j])
                    continue;

                int len = 0;
                int p = i, q = j;
                while(p != A.size() && q != B.size() && A[p] == B[q])
                {
                    ++p;
                    ++q;
                    ++len;
                }
                maxLen = std::max(maxLen, len);
            }
        }
        return maxLen;
    }
};

不过超时了:(,想想也觉得没这么容易


假设序列A的长度为m,序列B的长度为n。

考虑两个序列的相同子序列

A[i, i+1, ..., i+k]
B[j, j+1, ..., j+k]

将这两个子序列分别延伸到序列末尾

A[i, i+1, ..., m-1]
B[j, j+1, ..., n-1]

那么,可以把公共子序列看成是序列A[i : m-1]和B[j : n-1]的最长公共前缀

转换成动态规划,设dp[i][j]表示A[i : m-1]和B[j : n-1]的最长公共前缀,那么

if(A[i] == B[j])
    dp[i][j] = 1 + dp[i + 1][j + 1];
else
    dp[i][j] = 0;

如果A[i] == B[j],那么A[i : m-1]和B[j : n-1]的第一个位置已经相等,只需计算A[i+1 : m-1]和B[j+1 : n-1]的最长公共前缀

如果A[i] != B[j],那么A[i : m-1]和B[j : n-1]第一个位置就不相等,显然没有公共前缀

代码如下

class Solution {
public:
    int findLength(vector<int>& A, vector<int>& B) {
        vector<vector<int>> dp(A.size()+1, vector<int>(B.size()+1, 0));
        int maxLen = 0;
        for(int i = A.size() - 1; i >= 0; --i)
        {
            for(int j = B.size() - 1; j >= 0; --j)
            {
                if(A[i] == B[j])
                {
                    /* 更新dp[i][j] */
                    dp[i][j] = 1 + dp[i + 1][j + 1];
                    maxLen = std::max(maxLen, dp[i][j]);
                }
            }
        }
        return maxLen;
    }
};

当然了,既然是迭代法实现的动态规划,那么就能将动态规划数组降维

观察二维动态规划数组的更新情况,整个程序只用到了dp[i][j]和dp[i+1][j+1],又因为i在外层循环,所以可以改为dp[j] = dp[j+1],不过需要改变内层循环的遍历顺序

class Solution {
public:
    int findLength(vector<int>& A, vector<int>& B) {
        vector<int> dp(B.size() + 1, 0);
        int maxLen = 0;
        for(int i = A.size() - 1; i >= 0; --i)
        {
            /* 遍历顺序颠倒 */
            for(int j = 0; j < B.size(); ++j)
            {
                /* 相等和不相等两种情况 */
                dp[j] = (A[i] == B[j]) ? 1 + dp[j + 1] : 0;
                maxLen = std::max(maxLen, dp[j]);
            }
        }
        return maxLen;
    }
};

为了便于理解,可以

  • 把dp[j]看成dp[i][j],表示当前的i当前的j

  • 把dp[j+1]看成dp[i+1][j+1],表示上次循环的i和下次循环的j

假设有I1, J1, J11表示某次循环时的i,j和j+1,下一轮外层循环时i,j和j+1改为I2,J1,J11。

以下称为第一次和第二次循环

在第二次循环过程中,假设正要执行但还没有执行

dp[j] = (A[i] == B[j]) ? 1 + dp[j + 1] : 0;

写成上面符号的格式是

dp[J1] = (A[I2] == B[J1]) ? 1 + dp[J11] : 0;

因为j是从0到B.size()的,所以只有更新完dp[J1]后才会更新dp[J11],当准备更新dp[J1]时,dp[J11]还没有被更新

没有被更新的意思是dp[J11]的值在上次更新后没有被改变,上次更新时的i是I1,j是J11,所以dp[J11] == dp[I1][J11]

因为i是从A.size()到0的,所以I1 = I2 + 1,所以

dp[J11] == dp[I1][J11] == dp[I2 + 1][J11]

对于正要更新的dp[J1],此时的i是I2,所以

dp[J1] == dp[I2][J1]

所以上面的更新过程可以改为

dp[I2][J1] = (A[I2] == B[J1]) ? 1 + dp[I2 + 1][J11] : 0;

由于J11 == J1 + 1,所以

dp[I2][J1] = (A[I2] == B[J1]) ? 1 + dp[I2 + 1][J1 + 1] : 0;

形式和

dp[i][j] = (A[i] == B[j]) ? 1 + dp[i + 1][j + 1] : 0;

相同

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值