最长上升子序列-序列dp
- 什么是序列相关的 DP ?
- 序列相关 DP,顾名思义,就是将动态规划算法用于数组或者字符串上,进行相关具体问题的求解
- 何时可以使用序列相关的 DP?
- 当题目求解以下内容时,可以考虑使用序列相关的 DP
- 给定两个字符串,求最长/大的某种值
- 给定数组,求最长/大的某种值
此外,在使用序列相关的 DP 的时候,我们还需要注意到,这是一类的动态规划,所以需要满足动态规划的两种重要条件
最长上升子序列问题是一个经典的动态规划问题,目标是在给定序列中找到一个最长的升序子序列。
1016 最大上升子序列和
package acwing_plus.动规.最长上升子序列;
import java.util.Scanner;
/**
* @author ty
* @create 2023-03-31 10:00
*/
public class 最长上升子序列和 {
static int N = 1010;
static int[] a = new int[N];
static int[] f = new int[N];
static int n;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
for (int i = 1;i <= n;i++) {
a[i] = sc.nextInt();
}
for (int i = 1;i <= n;i++) {
f[i] = a[i];
for (int j = 1;j < i;j++) {
if (a[i] > a[j]) {
f[i] = Math.max(f[i],f[j] + a[i]);
}
}
}
int res = 0;
for (int i = 1;i <= n;i++) {
res = Math.max(res,f[i]);
}
System.out.println(res);
}
}
1010. 拦截导弹
第一问:最长下降子序列
第二问:多少个最长下降子序列能覆盖整个序列
package acwing_plus.动规.最长上升子序列;
import java.util.Scanner;
/**
* @author ty
* @create 2023-03-31 11:29
*/
public class 拦截导弹 {
static int N = 1010;
static int n;
static int[] q = new int[N];
static int[] f = new int[N];
static int[] g = new int[N];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String[] line = sc.nextLine().split(" ");
n = line.length;
for (int i = 0;i < n;i++) {
q[i] = Integer.parseInt(line[i]);
}
//先求最长下降子序列
int res = 0;
for (int i = n-1;i >= 0;i--) {
f[i] = 1;
for (int j = n-1;j > i;j--) {
//题目要求是不高于上一个炮弹,所以此处下降序列可以连续相等
if (q[i] >= q[j]) {
f[i] = Math.max(f[i],f[j] + 1);
}
}
res = Math.max(res,f[i]);
}
System.out.println(res);
//g数组存现有的子序列
//cnt表示当前子序列个数
int cnt = 0;
for (int i = 0;i < n;i++) {
int k = 0;
//找到第一个最长下降子序列最后一个值,大于等于q[i]的
while (k < cnt && g[k] < q[i]) k++;
g[k] = q[i];
//没有序列,能存储当前元素
if (k >= cnt) {
cnt++;
}
}
System.out.println(cnt);
}
}
187. 导弹防御系统
比1010变化:拦截系统新增可以单调上升的选择
即最少可以用多少个上升子序列和下降子序列将整个序列覆盖掉
此时不能用贪心,因为最开始还要面临选择上升还是下降,这是没法做到的,只能使用暴搜
package acwing_plus.动规.最长上升子序列;
import java.util.Scanner;
/**
* @author ty
* @create 2023-03-31 12:45
*/
public class 导弹防御系统 {
static int N = 55;
static int[] q = new int[N];
static int[] up = new int[N];
static int[] down = new int[N];
static int n;
//记录全局最小值
static int ans;
/**
* @param u 当前枚举到第几个数
* @param su 当前上升子序列的个数
* @param sd 当前下降子序列的个数
*/
private static void dfs(int u, int su, int sd) {
if (su + sd >= ans) return;//超出最小的ans
//找到方案
if (u == n) {
//更新最小方案数
ans = su + sd;
return;
}
//情况一,将当前数放到上升子序列中
int k = 0;
//上升子序列是找到第一个小于它的数
while (k < su && up[k] >= q[u]) {
k++;
}
int t = up[k];
up[k] = q[u];
//说明没有开辟新的上升子序列
if (k < su) {
dfs(u+1,su,sd);
}else dfs(u+1,su+1,sd);//开辟了新的上升子序列
//恢复现场
up[k] = t;
//情况二:将当前数放到下降子序列中
k = 0;
while (k < sd && down[k] <= q[u]) k++;
t = down[k];
down[k] = q[u];
//说明没有开辟新的下降子序列
if (k < sd) dfs(u+1,su,sd);
else dfs(u+1,su,sd+1);
down[k] = t;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while ((n = sc.nextInt()) != 0){
ans = n;
for (int i = 0;i < n;i++) {
q[i] = sc.nextInt();
}
dfs(0,0,0);
System.out.println(ans);
};
}
}
272.最长公共上升子序列
最长公共子序列
划分方式
n^3 TLE
package acwing_plus.动规.最长上升子序列;
import java.util.Scanner;
/**
* @author ty
* @create 2023-03-31 14:22
*/
public class 最长公共上升子序列 {
static int N = 3010;
static int n;
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);
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();
/*
f[i][j]表示所有第一个序列中前i个字母和第二个序列中前j个字母构成的
且以b[j]结尾的公共上升子序列的最长长度
f[i][j]来自于
情况一:不包含a[i]=>f[i-1][j]
情况二:包含a[i],前提a[i]==b[j]
->再按照倒数第二个数的位置进行划分
->倒数第二个数的位置可以取,空值(即只有b[j]一个元素),b[1],b[2]...b[j-1]
->需要满足对于上述位置k,b[j]>b[k]
=>f[i,j] = max(f[i,j],f[i,k]+1)
最终答案是max(f[n,i]),i = 1...n
*/
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]) {
//空集
f[i][j] = Math.max(f[i][j], 1);
for (int k = 1; k < j; k++) {
if (b[j] > b[k]) {
f[i][j] = Math.max(f[i][k] + 1, f[i][j]);
}
}
}
}
}
int res = 0;
for (int i = 1; i <= n; i++) {
res = Math.max(res, f[n][i]);
}
System.out.println(res);
}
}
优化
import java.util.Scanner;
/**
* @author ty
* @create 2023-03-31 14:22
*/
public class Main {
static int N = 3010;
static int n;
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);
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();
/*
f[i][j]表示所有第一个序列中前i个字母和第二个序列中前j个字母构成的
且以b[j]结尾的公共上升子序列的最长长度
f[i][j]来自于
情况一:不包含a[i]=>f[i-1][j]
情况二:包含a[i],前提a[i]==b[j]
->再按照倒数第二个数的位置进行划分
->倒数第二个数的位置可以取,空值(即只有b[j]一个元素),b[1],b[2]...b[j-1]
->需要满足对于上述位置k,b[j]>b[k]
=>f[i,j] = max(f[i,j],f[i,k]+1)
最终答案是max(f[n,i]),i = 1...n
*/
for (int i = 1; i <= n; i++) {
int maxv = 1;//表示a[i]==b[j],1到j-1在满足a[i]>b[k]条件下的最大值
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);
}
/*
这里的理解是,因为本质上之前更新的时候是b[k] < b[j]
转换为b[k]<a[i]是因为b[j]==a[j],即选择到a[i]
但是此时更新时,a[i]是我们将要寻找的b[j],可以理解为在执行到
a[i]==b[j]之前的更新,都是没有选用a[i]的情况下的
*/
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);
}
}