侧边栏壁纸
博主头像
GabrielxD

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

  • 累计撰写 628 篇文章
  • 累计创建 127 个标签
  • 累计收到 9 条评论

目 录CONTENT

文章目录

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

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

题目

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) {
                        f[i][j] = Math.max(f[i][j], 1);
                        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);
    }
}

优化

上面的代码时间复杂度是 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 中找到满足小于 a[i]a[i]f[i1][k]f[i-1][k] 的最大值,其中的条件 b[k]<a[i]b[k] < a[i] 是与 jj 无关的,因此,这一层循环可以提到外层用一个变量( mx )来维护。

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);
    }
}
0

评论区