最长上升子序列-序列dp
- 什么是序列相关的 DP ?
- 序列相关 DP,顾名思义,就是将动态规划算法用于数组或者字符串上,进行相关具体问题的求解
- 何时可以使用序列相关的 DP?
- 当题目求解以下内容时,可以考虑使用序列相关的 DP
- 给定两个字符串,求最长/大的某种值
- 给定数组,求最长/大的某种值
此外,在使用序列相关的 DP 的时候,我们还需要注意到,这是一类的动态规划,所以需要满足动态规划的两种重要条件
最长上升子序列问题是一个经典的动态规划问题,目标是在给定序列中找到一个最长的升序子序列。
问题描述:
给定一个序列,要求找到其中最长的一个升序子序列。也就是说,要找到一个子序列,使得其中的元素按照从小到大的顺序排列,并且长度最长。
解决方法:
一种常用的动态规划方法来解决最长上升子序列问题是使用一个辅助数组dp。dp[i]表示以第i个元素结尾的最长上升子序列的长度。
具体步骤如下:
- 初始化dp数组为1,所有的元素初始都有一个长度为1的子序列。
- 对于每个位置i,从0到i-1遍历前面的元素j:
- 如果第i个元素大于第j个元素,说明可以将第i个元素添加到以第j个元素结尾的子序列中,形成一个更长的子序列。
- 此时更新dp[i] = max(dp[i], dp[j] + 1),表示以第i个元素结尾的子序列的长度。
- 如果第i个元素大于第j个元素,说明可以将第i个元素添加到以第j个元素结尾的子序列中,形成一个更长的子序列。
- 遍历所有的dp[i],找到其中的最大值max_length,即为最长上升子序列的长度。
最后,根据dp数组的记录,可以逆向构造最长上升子序列的元素。
总结:
最长上升子序列问题可以通过动态规划的方法解决,使用dp数组记录以每个位置结尾的最长上升子序列长度,通过逐个比较元素大小,不断更新dp数组。最终得到最长上升子序列的长度,及其中的元素。这个问题是经典的算法问题,在实际应用中有广泛的应用。
概览
895 最长上升子序列-O(n^2)
集合表示:所有以a[i]结尾的最长上升子序列
属性为Max即长度的最大值
考虑如何计算,一般考虑最后一个点,可以取
空,a[0],a[1],…,a[i-1]
但是有的区间为空,当a[k] >= a[i]时,即不满足上升子序列
计算时:a[i]=Math.max(满足条件的a[k])
最终答案,遍历所有下标,选取最长的上升子序列
package acwing_plus.动规.最长上升子序列;
import java.util.Scanner;
/**
* @author ty
* @create 2023-03-30 21:35
*/
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] = 1;
for (int j = 1;j < i;j++) {
if (a[i] > a[j]) {
f[i] = Math.max(f[j] + 1,f[i]);
}
}
}
int res = 0;
for (int i = 1;i <= n;i++) {
//res是所有不同a[i]结尾的最大值
res = Math.max(res,f[i]);
}
System.out.println(res);
}
}
1017 怪盗基德的滑翔翼
LIS的双向求解,主要掌握求最长下降序列,即反向求解最长上升序列
package acwing_plus.动规.最长上升子序列;
import java.util.Scanner;
/**
* @author ty
* @create 2023-03-30 21:57
*/
public class 怪盗基德 {
static int N = 110;
static int n;
static int[] a = new int[N];
static int[] f = new int[N];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int T = sc.nextInt();
while (T-- > 0 ) {
n = sc.nextInt();
for (int i = 1;i <= n;i++) {
a[i] = sc.nextInt();
}
//正向求解LIS问题
int res = 0;
for (int i = 1;i <= n;i++) {
f[i] = 1;
for (int j = 1;j < i;j++) {
if (a[j] < a[i]){
f[i] = Math.max(f[j] + 1,f[i]);
}
}
res = Math.max(res,f[i]);
}
//反向求解LIS
for (int i = n;i >= 1;i--) {
f[i] = 1;
for (int j = n;j > i;j--) {
if (a[i] > a[j]) {
f[i] = Math.max(f[i],f[j] + 1);
}
}
res = Math.max(f[i],res);
}
System.out.println(res);
}
}
}
1014 登山
自己实现,由怪盗基德思想实现
package acwing_plus.动规.最长上升子序列;
import java.util.Scanner;
/**
* @author ty
* @create 2023-03-30 23:10
*/
public class 登山 {
static int N = 1010;
static int[] a = new int[N];
static int[] l = new int[N];
static int[] r = 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();
}
int res = 0;
//求最长下降子序列长度
for (int i = n;i >= 1;i-- ) {
r[i] = 1;
for (int j = n;j > i;j--) {
if (a[i] > a[j]) {
r[i] = Math.max(r[j] + 1,r[i]);
}
}
}
//求最长上升子序列长度
for (int i = 1;i <= n;i++) {
l[i] = 1;
for (int j = 1;j < i;j++) {
if (a[i] > a[j]) {
l[i] = Math.max(l[i],l[j] + 1);
}
}
}
for (int i = 1;i <= n;i++) {
res = Math.max(l[i]+r[i] - 1,res);
}
System.out.println(res);
}
}
482 合唱队形
登山的变体,求整个队列个数-max(每个点的最大上升序列+最大下降序列-1)
package acwing_plus.动规.最长上升子序列;
import java.util.Scanner;
/**
* @author ty
* @create 2023-03-31 0:36
*/
public class 合唱队形 {
static int N = 110;
static int n;
static int[] a = new int[N];
static int[] l = new int[N];
static int[] r = new 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++) {
l[i] = 1;
for (int j = 1;j < i;j++) {
if (a[i] > a[j]) {
l[i] = Math.max(l[i],l[j] + 1);
}
}
}
for (int i = n;i >= 1;i--) {
r[i] = 1;
for (int j = n;j >= i;j--) {
if (a[i] > a[j]) {
r[i] = Math.max(r[i],r[j] + 1);
}
}
}
int res = 0;
for (int i = 1;i <= n;i++) {
res = Math.max(res,l[i] + r[i] -1);
}
System.out.println(n - res);
}
}
1012 友好城市
package acwing_plus.动规.最长上升子序列;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;
/**
* @author ty
* @create 2023-03-31 9:40
*/
public class 友好城市 {
static int N = 5010;
static Edge[] a = new Edge[N];
static int[] f = new int[N];
static int INF = (int)-1e9;
static class Edge {
int u,d;
public Edge(int u, int d) {
this.u = u;
this.d = d;
}
}
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++) {
int x = sc.nextInt();
int y = sc.nextInt();
a[i] = new Edge(x,y);
}
//按下面排好序
Arrays.sort(a, 1, n + 1, new Comparator<Edge>() {
@Override
public int compare(Edge o1, Edge o2) {
return o1.d - o2.d;
}
});
//上面的点一定是上升序列,才能成立
//此处寻找最长上升子序列
for (int i = 1;i <= n;i++) {
f[i] = 1;
for (int j = 1;j < i;j++) {
if (a[i].u > a[j].u) {
f[i] = Math.max(f[i],f[j] + 1);
}
}
}
int res = 0;
for (int i = 1;i <= n;i++) {
res = Math.max(res,f[i]);
}
System.out.println(res);
}
}