AcWing刷题——最长公共上升子序列(动态规划 线性DP 前缀和)
题目描述
熊大妈的奶牛在小沐沐的熏陶下开始研究信息题目。
小沐沐先让奶牛研究了最长上升子序列,再让他们研究了最长公共子序列,现在又让他们研究最长公共上升子序列了。
小沐沐说,对于两个数列 A
和 B
,如果它们都包含一段位置不一定连续的数,且数值是严格递增的,那么称这一段数是两个数列的公共上升子序列,而所有的公共上升子序列中最长的就是最长公共上升子序列了。
奶牛半懂不懂,小沐沐要你来告诉奶牛什么是最长公共上升子序列。
不过,只要告诉奶牛它的长度就可以了。
数列 A
和 B 的长度均不超过 3000
。
输入格式
第一行包含一个整数 N
,表示数列 A,B
的长度。
第二行包含 N
个整数,表示数列 A
。
第三行包含 N
个整数,表示数列 B
。
输出格式
输出一个整数,表示最长公共上升子序列的长度。
数据范围
1≤N≤3000
,序列中的数字均不超过 231−1
。
输入样例:
4
2 2 1 3
2 1 2 3
输出样例:
2
分析
一开始自己的思路是想把求最长公共子序列和最长上升子序列的方法结合在一起实现,后面发现自己的思路不行,因为实在想不出,就去看题解了,毕竟也是学算法没多久,哎。看了y总的思路:原来y总定义f[i][j]的状态就跟我不一样。
原来是我根本没有往回搜索,要知道找最长上升子序列是一定要往回看的,不然不能确定当前的就一定是最长的那个,这一点我在写最长上升子序列时还特别注意了下,可是到了这又忘了,说到底还是自己练的太少了。
闫式dp分析法:
自己的代码
1 import java.util.Scanner; 2 public class Main { 3 public static void main(String[] args) { 4 Scanner input = new Scanner(System.in); 5 int N = input.nextInt(); 6 int[] A = new int[N]; 7 int[] B = new int[N]; 8 int[][] dp = new int[N + 10][N + 10]; 9 for (int i = 0; i < N; i++) { 10 A[i] = input.nextInt(); 11 } 12 for (int i = 0; i < N; i++) { 13 B[i] = input.nextInt(); 14 } 15 16 // 初始化 17 for (int i = 0; i < N; i++) { 18 dp[0][i] = 0; 19 } 20 21 for (int i = 0; i < N; i++) { 22 dp[i][0] = 0; 23 } 24 25 for (int i = 1; i <= N; i++) { 26 int curMax = Integer.MIN_VALUE; 27 for (int j = 1; j <= N; j++) { 28 if (A[i - 1] == B[j - 1]) { 29 if (A[i - 1] <= curMax) { 30 dp[i][j] = 1; 31 curMax = A[i - 1]; 32 continue; 33 } 34 dp[i][j] = dp[i - 1][j - 1] + 1; 35 curMax = A[i - 1]; 36 } else { 37 // 查看前面是A数列少一位之后更长,还是B数列少一位之后更长,选较长的那个作为此时的长度 38 dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); 39 } 40 } 41 } 42 43 System.out.println(dp[N][N]); 44 } 45 }
运行结果,看到结果就知道我自己的代码正确性都谈不上,还是要多看看大佬写代码的思路,不过每次都要有自己的思路在里面,不然永远也学不会算法
AC代码——时间复杂度为O(n ^ 3)
没想到这个复杂度也能过,可能是数据有点弱吧,哈哈!(不过我们还是应该相信科学,不能相信玄学,要尽量掌握怎么优化dp,这才是我们学算法的人该有的精神)
1 import java.util.Scanner; 2 3 public class Main { 4 private static final int N = 3010; 5 public static void main(String[] args) { 6 Scanner input = new Scanner(System.in); 7 int n = input.nextInt(); 8 int[] A = new int[N]; 9 int[] B = new int[N]; 10 11 for (int i = 1; i <= n; i++) A[i] = input.nextInt(); 12 for (int i = 1; i <= n; i++) B[i] = input.nextInt(); 13 14 // 定义状态:f[i][j] 代表A[1..i]和B[1..j]两个序列中,以b[j]结尾的最长公共上升子序列的长度 15 int[][] f = new int[N][N]; 16 17 // 转移方程 18 for (int i = 1; i <= n; i++) { 19 for (int j = 1; j <= n; j++) { 20 f[i][j] = f[i - 1][j]; 21 if (A[i] == B[j]) { 22 int maxv = 1; // 这里长度最短也要是1 23 for (int k = 1; k < j; k++) { 24 if (A[i] > B[k]) { 25 maxv = Math.max(maxv, f[i - 1][k] + 1); 26 } 27 } 28 // 更新 29 f[i][j] = Math.max(f[i][j], maxv); 30 } 31 } 32 } 33 34 int res = 1; 35 // 最终答案枚举子序列结尾取最大值即可。 36 for (int i = 1; i <= n; i++) { 37 res = Math.max(res, f[n][i]); 38 } 39 40 41 System.out.println(res); 42 } 43 }
AC代码——时间复杂度为O(n ^ 2)
以下这个代码一开始不是很理解,是看了AcWing官网上y总给的解释
1 import java.util.Scanner; 2 3 public class Main { 4 private static final int N = 3010; 5 public static void main(String[] args) { 6 Scanner input = new Scanner(System.in); 7 int n = input.nextInt(); 8 int[] A = new int[N]; 9 int[] B = new int[N]; 10 11 for (int i = 1; i <= n; i++) A[i] = input.nextInt(); 12 for (int i = 1; i <= n; i++) B[i] = input.nextInt(); 13 14 // 定义状态:f[i][j] 代表A[1..i]和B[1..j]两个序列中,以b[j]结尾的最长公共上升子序列的长度 15 int[][] f = new int[N][N]; 16 17 // 转移方程 18 for (int i = 1; i <= n; i++) { 19 int maxv = 1; 20 for (int j = 1; j <= n; j++) { 21 f[i][j] = f[i - 1][j]; 22 if (A[i] == B[j]) { 23 f[i][j] = Math.max(f[i][j], maxv); 24 } 25 if (A[i] > B[j]) { 26 maxv = Math.max(maxv, f[i - 1][j] + 1); 27 } 28 } 29 } 30 31 int res = 0; 32 // 最终答案枚举子序列结尾取最大值即可。 33 for (int i = 1; i <= n; i++) { 34 res = Math.max(res, f[n][i]); 35 } 36 37 38 System.out.println(res); 39 } 40 }
AcWing官网题目链接:https://www.acwing.com/problem/content/description/274/