侧边栏壁纸
博主头像
GabrielxD

列車は必ず次の駅へ。では舞台は?私たちは?

  • 累计撰写 674 篇文章
  • 累计创建 128 个标签
  • 累计收到 20 条评论

目 录CONTENT

文章目录

【动态规划, LIS, LCS】最长公共上升子序列「动态规划之LIS模型」

GabrielxD
2023-02-12 / 0 评论 / 0 点赞 / 91 阅读 / 1,685 字
温馨提示:
本文最后更新于 2023-05-04,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

题目

272. 最长公共上升子序列 - AcWing题库


熊大妈的奶牛在小沐沐的熏陶下开始研究信息题目。

小沐沐先让奶牛研究了最长上升子序列,再让他们研究了最长公共子序列,现在又让他们研究最长公共上升子序列了。

小沐沐说,对于两个数列 AABB ,如果它们都包含一段位置不一定连续的数,且数值是严格递增的,那么称这一段数是两个数列的公共上升子序列,而所有的公共上升子序列中最长的就是最长公共上升子序列了。

奶牛半懂不懂,小沐沐要你来告诉奶牛什么是最长公共上升子序列。

不过,只要告诉奶牛它的长度就可以了。

数列 AABB 的长度均不超过 30003000

输入格式

第一行包含一个整数 NN ,表示数列 ABA,B 的长度。

第二行包含 NN 个整数,表示数列 AA

第三行包含 NN 个整数,表示数列 BB

输出格式

输出一个整数,表示最长公共上升子序列的长度。

数据范围

1N30001 \le N \le 3000 ,序列中的数字均不超过 23112^{31}-1

输入样例:

4
2 2 1 3
2 1 2 3

输出样例:

2

解题

方法一:动态规划 前缀和

思路

注意:以下分析均基于两字符串下标从 11 开始。

思维过程:

image-20230212224436191

动态规划:

  • 状态定义:f[i][j]f[i][j] 表示由字符串 AA 中前 ii 个字符与字符串 BB 中前 jj 个字符构成,且以 B[j]B[j] 结尾的公共上升子序列的最长长度。
  • 状态转移方程:f[i][j]={f[i1][j]A[i]B[j]max(f[i1][j],f[i1][k]),k[1,j1]A[i]=B[j]f[i][j] = \begin{cases} f[i-1][j] & A[i] \neq B[j] \\ \max(f[i-1][j], f[i-1][k]), \,\, k \in [1, j-1] & A[i] = B[j] \end{cases}
  • 初始状态:进行第一次状态转移前,所有位置的公共上升子序列的最大长度均为 11

代码

import java.util.*;
import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException {
        StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
        in.nextToken();
        int n = (int) in.nval;
        int[] a = new int[n + 1], b = new int[n + 1];
        for (int i = 1; i <= n; ++i) {
            in.nextToken();
            a[i] = (int) in.nval;
        }
        for (int i = 1; i <= n; ++i) {
            in.nextToken();
            b[i] = (int) in.nval;
        }
        int[][] f = new int[n + 1][n + 1];
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= n; ++j) {
                f[i][j] = f[i - 1][j];
                if (a[i] == b[j]) {
                    for (int k = 1; k < j; ++k) {
                        if (b[k] < b[j]) f[i][j] = Math.max(f[i][j], f[i - 1][k] + 1);
                    }
                }
            }
        }
        int mx = 0;
        for (int i = 1; i <= n; ++i) mx = Math.max(mx, f[n][i]);
        System.out.println(mx);
    }
}
#include <iostream>

using namespace std;

const int N = 3010;
int n;
int a[N], b[N];
int f[N][N];

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
    for (int i = 1; i <= n; ++i) scanf("%d", &b[i]);
    for (int i = 1; i <= n; ++i) {
        int mx = 1;
        for (int j = 1; j <= n; ++j) {
            f[i][j] = f[i - 1][j];
            if (b[j] < a[i]) mx = max(mx, f[i - 1][j] + 1);
            else if (a[i] == b[j]) f[i][j] = max(f[i][j], mx);
        }
    }
    int mx = 0;
    for (int i = 1; i <= n; ++i) mx = max(mx, f[n][i]);
    printf("%d\n", mx);

    return 0;
}

优化

上面的代码时间复杂度是 O(n3)O(n^3) ,很明显会超时。

我们发现,只有在 a[i]=b[j]a[i] = b[j] 时,才会进入第三层循环。把循环中的所有 b[j]b[j] 都换为 a[i]a[i] 就得到了:

if (a[i] == b[j]) {
    for (int k = 1; k < j; ++k) {
        f[i][j] = Math.max(f[i][j], 1);
        if (b[k] < a[i]) f[i][j] = Math.max(f[i][j], f[i - 1][k] + 1);
    }
}

该循环的含义是:1j11 \sim j-1 中找到满足 b[k]<a[i]b[k] < a[i]f[i1][k]f[i-1][k] 的最大值,其中的条件 b[k]<a[i]b[k] < a[i] 是与 jj 无关的,故可以尝试把 kk 换成 jj 来把这一层循环提到外层用一个变量 mx 来维护,这样做时,由于枚举 jj 的顺序是由 1n1 \dots n 正向枚举的,所以 b[j]<a[i]b[j] < a[i] 总是在 a[i]=b[j]a[i] = b[j] 之前成立,也就是说当 a[i]=b[j]a[i] = b[j] 成立时, mx 一定是「 k[1,j1]k \in [1, j-1] 时满足 b[k]<a[i]b[k] < a[i]f[i1][k]f[i-1][k] 的最大值」,证明了可以把求最大值的循环放到外层循环来做,时间复杂度降为 O(n2)O(n^2)

import java.util.*;
import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException {
        StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
        in.nextToken();
        int n = (int) in.nval;
        int[] a = new int[n + 1], b = new int[n + 1];
        for (int i = 1; i <= n; ++i) {
            in.nextToken();
            a[i] = (int) in.nval;
        }
        for (int i = 1; i <= n; ++i) {
            in.nextToken();
            b[i] = (int) in.nval;
        }
        int[][] f = new int[n + 1][n + 1];
        for (int i = 1; i <= n; ++i) {
            int mx = 1;
            for (int j = 1; j <= n; ++j) {
                f[i][j] = f[i - 1][j];
                if (b[j] < a[i]) mx = Math.max(mx, f[i - 1][j] + 1);
                else if (a[i] == b[j]) f[i][j] = Math.max(f[i][j], mx);
            }
        }
        int mx = 0;
        for (int i = 1; i <= n; ++i) mx = Math.max(mx, f[n][i]);
        System.out.println(mx);
    }
}
#include <iostream>

using namespace std;

const int N = 3010;
int n;
int a[N], b[N];
int f[N][N];

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
    for (int i = 1; i <= n; ++i) scanf("%d", &b[i]);
    for (int i = 1; i <= n; ++i) {
        int mx = 1;
        for (int j = 1; j <= n; ++j) {
            f[i][j] = f[i - 1][j];
            if (b[j] < a[i]) mx = max(mx, f[i - 1][j] + 1);
            else if (a[i] == b[j]) f[i][j] = max(f[i][j], mx);
        }
    }
    int mx = 0;
    for (int i = 1; i <= n; ++i) mx = max(mx, f[n][i]);
    printf("%d\n", mx);

    return 0;
}
0

评论区