经典DP问题之最长上升子序列和最长公共子序列

Posted mgd666

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了经典DP问题之最长上升子序列和最长公共子序列相关的知识,希望对你有一定的参考价值。

经典DP问题之最长上升子序列和最长公共子序列

在DP问题中,最长上升子序列(LIS)和最长公共子序列(LCS)无疑是最经典的入门题目,充分体现了DP的思想。

最长上升子序列(LIS)

  • 题目描述

给定一个长度为N的数列,求数值严格单调递增的子序列的长度最长是多少。

  • 示例

input:

7

3 1 2 1 8 5 6

output:

4

(答案是:1 2 5 6)

首先解释一下什么叫做子序列,可以理解为在这个数列中从前往后挑数字组成数组,可以连着挑,可以跳着挑。

  • 思路

这个题目是典型的DP问题,首先我们先确定状态表示,集合f[i]表示的是以第i个数为结尾的上升子序列,属性是求Max。所以我们的f[i]表示的是:以第i个数字为结尾的上升子序列的长度的最大值。接下来考虑状态计算,我们先对集合进行划分,这一题的划分方式基本都是一个套路,就是以分割以前i-1个数字为结尾的上升子序列的长度的最大值的集合,比如说,以第1个数为结尾,第二个数为结尾。。。。。。,第i-1个数为结尾,集合中的每一个元素都+1,代表以第i个数字为结尾的上升子序列的长度的最大值。求出这个集合中的max即为答案。在这个集合中,有的数据是大于a[i]的,此时我们不更新它即可。

  • 代码
//数据范围 1≤N≤1000
#include <iostream>
using namespace std;
const int N = 1010;
int f[N];
int a[N];

int main() {
    int n = 0;
    cin >> n;
    for (int i = 1;i <= n; i++) cin >> a[i];
    for (int i = 1;i <= n; i++) {
        f[i] = 1;
        for (int j = 1;j < i; j++) {
            if (a[j] < a[i]) {
                f[i] = max(f[i],f[j] + 1);
            }
        }
    }
    
    int res = 0;
    for (int i = 1;i <= n; i++) res = max(res,f[i]);
    cout << res;
    return 0;
}

最长公共子序列(LCS)

  • 题目描述

给定两个长度分别为N和M的字符串A和B,求既是A的子序列,又是B的子序列的字符串长度最长是多少。

  • 示例

input:

A:a c b d

B:a b e d c

output:

3

(a b d)

  • 思路

这也是个典型的DP问题,但是思路比LIS更难想。首先我们确定状态表示,集合f[i,j]表示所有由A字符串的前i个字母和B字符串的前j个字母构成的公共子序列的合集,属性是求Max,所以f[i,j]代表所有由A字符串的前i个字母和B字符串的前j个字母构成的公共子序列的长度的最大值。接下来考虑状态计算,这种类型的题目集合划分的思路更难想到,记住就好了。我们将集合f[i,j]先划分成四个区域,分别是第一种情况,不选A字符串第i个字母,不选B字符串第j个字母,第二种情况,不选择A字符串第i个字母,选择B字符串的第j个字母,第三种情况,选择A字符串的第i个字母,不选择B字符串的第j个字母,第四种情况,选择A字符串的第i个字母,选择B字符串的第j个字母。下面我们来逐一分析

  • 第四种情况

两个字母都选的话,是需要特判的,两个字母相同时才考虑这种情况

  • 第二,第三种情况

f[i-1,j]和f[i,j-1]两种情况,我们需要明白f[i,j]代表的是所有由A字符串的前i个字母和B字符串的前j个字母构成的公共子序列的合集,这个合集中包含了许多情况,有些字母并不符合要求也不更新,但是它一定包含a[i]出现,b[i]也出现的情况,所以第一种情况包含在了第二,三种情况中。这代表着四种情况会有重合的,但是我们的属性是求max,所以效果都是一样的。

  • 代码
#include <iostream>
using namespace std;
const int N = 1010;
char a[N],b[N];
int f[N][N];

int main() {
    int n,m;
    cin >> n >> m;
    scanf("%s%s",a+1,b+1);
    
    for (int i = 1;i <= n; i++) {
        for (int j = 1;j <= m; j++) {
            f[i][j] = max(f[i-1][j],f[i][j-1]);
            if (a[i] == b[j]) f[i][j] = max(f[i][j],f[i-1][j-1] + 1);
        }
    }
    
    cout <<f[n][m];
    return 0;
}

总结

DP问题就按照考虑状态表示和状态计算的套路来做,想好状态转移方程。DP类型的题目没有一个固定的模版,还是要多积累,多总结,根据DP问题的性质来思考状态转移方程。

以上是关于经典DP问题之最长上升子序列和最长公共子序列的主要内容,如果未能解决你的问题,请参考以下文章

最长上升子序列2

动态规划线性dp问题总结:数字三角形最长上升子序列最长公共子序列最短编辑距离 题解与模板

最长上升子序列问题/最长公共子序列问题

最长公共子序列的应用

力扣之最长上升子序列 2022-02-28~03-06

最长上升子序列(LIS经典变型) dp学习~5