两个数组分别用a
、b
来表示,长度都为n
。
状态定义:f[i][j]
,表示a[1...i]
和b[1...j]
之间的、且以b[j]
结尾的最长公共上升子序列的长度。
状态转移方程:
分成两种情况来计算:(1)公共上升子序列不包含a[i]
,则对应的问题等价于求解f[i-1][j]
;(2)公共上升子序列包含a[i]
,此种情况成立的条件是a[i]
与b[j]
相等,则转化成求解以当前子序列的倒数第二个数为结尾的子问题,因此这里需对数组b
中位于b[j]
前面的、且值小于b[j]
的(用k
表示该角标)f[i-1][k]
进行枚举,找出最大值。
(1) 朴素写法,时间复杂度$O(n^3)$
import java.util.*;
public class Main{
static int N = 3010;
static int[] a = new int[N];
static int[] b = new int[N];
static int[][] f = new int[N][N];
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
for(int i = 1; i <= n; i ++) a[i] = sc.nextInt();
for(int i = 1; i <= n; i ++) b[i] = sc.nextInt();
// dp
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++){
f[i][j] = f[i - 1][j];
int maxv = 0;
if(a[i] == b[j]){
maxv = 1;
for(int k = 1; k < j; k ++)
if(b[k] < b[j])
maxv = Math.max(maxv, f[i - 1][k] + 1);
}
f[i][j] = Math.max(f[i][j], maxv);
}
int res = 0;
for(int i = 1; i <= n; i ++) res = Math.max(res, f[n][i]);
System.out.println(res);
}
}
(2) 优化后的写法,时间复杂度$O(n^2)$
import java.util.*;
public class Main{
static int N = 3010;
static int[] a = new int[N];
static int[] b = new int[N];
static int[][] f = new int[N][N];
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
for(int i = 1; i <= n; i ++) a[i] = sc.nextInt();
for(int i = 1; i <= n; i ++) b[i] = sc.nextInt();
// dp
for(int i = 1; i <= n; i ++){
int maxv = 1;
for(int j = 1; j <= n; j ++){
f[i][j] = f[i - 1][j];
if(a[i] == b[j]) f[i][j] = Math.max(f[i][j], maxv);
if(b[j] < a[i]) maxv = Math.max(maxv, f[i - 1][j] + 1);
}
}
int res = 0;
for(int i = 1; i <= n; i ++) res = Math.max(res, f[n][i]);
System.out.println(res);
}
}