动态规划篇——线性DP

本次我们介绍动态规划篇的线性DP,我们会从下面几个角度来介绍:

  • 数字三角形
  • 最长上升子序列I
  • 最长上升子序列II
  • 最长公共子序列
  • 最短编辑距离

数字三角形

我们首先介绍一下题目:

/*题目概述*/

给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层

要求找出一条路径,使路径上的数字的和最大。

        7
3 8
8 1 0
2 7 4 4
4 5 2 6 5 /*具体需求*/ // 输入格式 第一行包含整数 n,表示数字三角形的层数。 接下来 n 行,每行包含若干整数,其中第 i 行表示数字三角形第 i 层包含的整数。 // 输出格式 输出一个整数,表示最大的路径数字和。 // 数据范围 1 ≤ n ≤ 500
−10000 ≤ 三角形中的整数 ≤ 10000 // 输入样例: 5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5 // 输出样例: 30

然后我们进行分析:

/*题目分析*/

我们采用DP思想
首先我们采用a[i][j]来表示第i行,第j列的数字;我们采用f[i][j]表示到达第i行第j列的路径最大值
那么我们的f[i][j]就有两条来源,分别来自于f[i][j]的左上和右上,也就是f[i-1][j-1]和f[i-1][j]
那么我们的当前值f[i][j]的最大值也就是左上和右上的最大值加上当前a[i][j]即可,注意每一行都是最大值,所以前面f[i][j]也是最大值 注意:由于上面操作涉及到j-1和j,可能会涉及边界问题,为了减少if判断条件,我们的操作从下标为1开始!

我们给出具体代码:

import java.util.Scanner;

public class NumberTriangle {

    final static int N =100010;
final static int INF = Integer.MIN_VALUE/2; // 提前设置信息,a为当前值,f为路径max
static int n;
static int[][] a = new int[N][N];
static int[][] f = new int[N][N]; public static void main(String[] args) { Scanner scanner = new Scanner(System.in); n = scanner.nextInt(); // a赋值
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
a[i][j] = scanner.nextInt();
}
} // f初始值(注意,这里的初始值需要联通边界也设置好初始值)
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= i+1; j++) {
f[i][j] = INF;
}
} // 开始DP
f[1][1] = a[1][1];
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= i; j++) {
f[i][j] = Math.max(f[i-1][j-1],f[i-1][j]) + a[i][j];
}
} // 提供返回值即可
int res = INF;
for (int i = 1; i <= n; i++) {
res = Math.max(res,f[n][i]);
} System.out.println(res);
}
}

最长上升子序列I

我们首先介绍一下题目:

/*题目概述*/

给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。

/*具体需求*/

// 输入格式

第一行包含整数 N。

第二行包含 N 个整数,表示完整序列。

// 输出格式

输出一个整数,表示最大长度。

// 数据范围

1 ≤ N ≤ 1000,
−109 ≤ 数列中的数 ≤ 109 // 输入样例: 7
3 1 2 1 8 5 6 // 输出样例: 4

然后我们进行分析:

/*题目分析*/

我们采用DP思想
我们采用a[i]表示第i个数的值,我们采用f[i]表示以当前值结尾的最长子序列长度 那么我们就需要采用双重循环,第一层循环用来遍历i,更新f[i];第二层循环用来查找i之前的j,判断j<i,则进行f[i]更新

我们给出具体代码:

import java.util.Scanner;

public class Main {

    final static int N = 1010;

    static int n;
static int[] a = new int[N];
static int[] f = new int[N]; public static void main(String[] args) {
Scanner scanner = new Scanner(System.in); n = scanner.nextInt(); // 赋值
for (int i = 1; i <= n; i++) {
a[i] = scanner.nextInt();
} // 开始DP
for (int i = 1; i <= n; i++) {
// 最开始只有他自己,默认为1
f[i] = 1;
// 二重循环,更新fi
for (int j = 1; j < i; j++) {
if (a[j] < a[i]) 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);
}
}

最长上升子序列II

我们这里对最长上升子序列进行一个优化处理:

/*优化思路*/

我们在之前是与所有小于该点的数进行一一比较,也就是双循环

我们可以采用q数组来存放不同子序列长度下的最小值来作为判定条件,同时我们采用二分查找来优化查找时间复杂度

/*代码展示*/

import java.util.Scanner;

public class Main {

    final static int N = 100010;

    // a存放当前数组,q存放每种长度的最长上升子序列中结尾的最小值
static int n;
static int[] a = new int[N];
static int[] q = new int[N]; public static void main(String[] args) { Scanner scanner = new Scanner(System.in); n = scanner.nextInt(); for (int i = 0; i < n; i++) {
a[i] = scanner.nextInt();
} // 我们首先需要设置q的前置条件,我们将长度设置0,将q[0]设置为负无穷以便于a的值可以存放进q中
int len = 0;
q[0] = -(int)-2e9; // 一个数可以接在什么位置,用二分来寻找每种长度的结尾的最小值比这个数小的的位置,然后长度加1,
// 就是新的最长上升子序列的长度
for(int i = 0 ; i < n ; i ++ ){
int l = 0,r = len;
while(l < r){
int mid = l + r + 1 >> 1;
if(q[mid] < a[i]) l = mid;
else r = mid - 1;
}
// 找到位置之后更新长度
len = Math.max(len, r + 1); // 比如因为找到的数q[4]是小于的a最大的数,所以后面的一个数q[5]就是大于等于这个数
// 然后我们可以接在q[4]后面,所以现在长度是5,变成q[5],然后因为我们的这个数是小于或者等于q[5]
// 所以直接将值赋上就行
q[r + 1] = a[i];
} System.out.println(len);
}
}

最长公共子序列

我们首先介绍一下题目:

/*题目概述*/

给定两个长度分别为 N 和 M 的字符串 A 和 B,求既是 A 的子序列又是 B 的子序列的字符串长度最长是多少。

/*具体需求*/

// 输入格式

第一行包含两个整数 N 和 M。

第二行包含一个长度为 N 的字符串,表示字符串 A。

第三行包含一个长度为 M 的字符串,表示字符串 B。

字符串均由小写字母构成。

// 输出格式

输出一个整数,表示最大长度。

// 数据范围

1 ≤ N, M ≤ 1000

// 输入样例:

4 5
acbd
abedc // 输出样例: 3

然后我们进行分析:

/*题目分析*/

我们采用DP思想
我们使用f[i][j]来表示a字符串前i个字符和b字符串前j个字符之间的最大子序列长度 那么我们希望用之前的f来更新最新的f,我们主要分为四种状态;
f[i][j] = f[i-1][j-1]
f[i][j] = f[i-1][j]
f[i][j] = f[i][j-1]
f[i][j] = f[i-1][j-1]+1 我们需要注意的是:
f[i-1][j]和f[i][j-1]已经涵括了f[i-1][j-1],所以我们可以少写一种情况
f[i][j] = f[i-1][j-1]+1情况只有当a[i]==b[i]时才会触发

我们给出具体代码:

import java.util.Scanner;

public class Main {

    final static int N = 1010;

    static int n,m;
static char[] a = new char[N];
static char[] b = new char[N];
static int[][] f = new int[N][N]; public static void main(String[] args) { Scanner scanner = new Scanner(System.in); // 赋值 n = scanner.nextInt(); m = scanner.nextInt(); String A = scanner.next();
for (int i = 1; i <= n; i++) {
a[i] = A.charAt(i-1);
} String B = scanner.next();
for (int i = 1; i <= m; i++) {
b[i] = B.charAt(i-1);
} // DP算法
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
// 第一种情况:f[i-1][j]和f[i][j-1]
f[i][j] = Math.max(f[i-1][j],f[i][j-1]);
// 第二种情况:f[i-1][j-1] + 1
if (a[i] == b[j]) f[i][j] = Math.max(f[i-1][j-1]+1,f[i][j]);
}
} // 输出
System.out.println(f[n][m]); }
}

最短编辑距离

我们首先介绍一下题目:

/*题目概述*/

给定两个字符串 A 和 B,现在要将 A 经过若干操作变为 B,可进行的操作有:

删除–将字符串 A 中的某个字符删除。
插入–在字符串 A 的某个位置插入某个字符。
替换–将字符串 A 中的某个字符替换为另一个字符。
现在请你求出,将 A 变为 B 至少需要进行多少次操作。 /*具体需求*/ // 输入格式 第一行包含整数 n,表示字符串 A 的长度。 第二行包含一个长度为 n 的字符串 A。 第三行包含整数 m,表示字符串 B 的长度。 第四行包含一个长度为 m 的字符串 B。 字符串中均只包含大小写字母。 // 输出格式 输出一个整数,表示最少操作次数。 // 数据范围 1 ≤ n,m ≤ 1000 // 输入样例: 10
AGTCTGACGC
11
AGTAAGTAGGC // 输出样例: 4

然后我们进行分析:

/*题目分析*/

我们采用DP思想
这里的DP思想其实和最长公共子序列很相似
我们使用f[i][j]来表示a字符串前i个字符和b字符串前j个字符之间进行匹配的最小操作数 我们需要提前设置一下初始值:
f[i][0]表示a的前i个字符和b为空时,这时我们需要对a进行i次减法:f[i][0] = i;
f[0][j]表示a为空和b的前j个字符时,这时我们需要对a进行i次加法:f[0][j] = i; 那么我们希望用之前的f来更新最新的f,我们主要分为两种状态;
当i和j变更时,我们需要对a做添加或删除:f[i][j] = Math.min(f[i - 1][j] + 1, f[i][j - 1] + 1);
当i和j同时变更,且a[i]==b[j],这时不需要操作:(a[i] == b[j]) f[i][j] = Math.min(f[i][j],f[i - 1][j - 1]);
但是如果不相等,我们需要进行修改操作:else f[i][j] = Math.min(f[i][j],f[i - 1][j - 1] + 1);

我们给出具体代码:

import java.util.*;

public class UpdateShort{
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
int N = 1010;
char[] a = new char[N];
char[] b = new char[N];
int[][] f = new int[N][N]; int n = scannernextInt();
String A = scanner.next();
int m = scanner.nextInt();
String B = scanner.next(); for(int i = 1 ; i <= n ; i ++ ) {
a[i] = A.charAt(i - 1);
f[i][0] = i; // 处理边界,字符串b是0,a进行n次删除
}
for(int i = 1 ; i <= m ; i ++ ){
b[i] = B.charAt(i - 1);
f[0][i] = i; // 处理边界,字符串a是0,a进行m次增加
} for(int i = 1 ; i <= n ; i ++ ){
for(int j = 1 ; j <= m ; j ++ ){
// 删除和增加操作
f[i][j] = Math.min(f[i - 1][j] + 1, f[i][j - 1] + 1);
// 最后一个数相同,不用进行修改操作,则不用加1
if(a[i] == b[j]) f[i][j] = Math.min(f[i][j],f[i - 1][j - 1]);
else f[i][j] = Math.min(f[i][j],f[i - 1][j - 1] + 1); // 修改操作
}
}
System.out.println(f[n][m]);
}
}

结束语

好的,关于动态规划篇的线性DP就介绍到这里,希望能为你带来帮助~

动态规划篇——线性DP的更多相关文章

  1. 动态规划_线性dp

    https://www.cnblogs.com/31415926535x/p/10415694.html 线性dp是很基础的一种动态规划,,经典题和他的变种有很多,比如两个串的LCS,LIS,最大子序 ...

  2. 【线性DP】数字三角形

    题目链接 原题链接 题目描述 给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大. 7 3 ...

  3. 动态规划——线性dp

    我们在解决一些线性区间上的最优化问题的时候,往往也能够利用到动态规划的思想,这种问题可以叫做线性dp.在这篇文章中,我们将讨论有关线性dp的一些问题. 在有关线性dp问题中,有着几个比较经典而基础的模 ...

  4. 线性DP总结(LIS,LCS,LCIS,最长子段和)

    做了一段时间的线性dp的题目是时候做一个总结 线性动态规划无非就是在一个数组上搞嘛, 首先看一个最简单的问题: 一,最长字段和 下面为状态转移方程 for(int i=2;i<=n;i++) { ...

  5. 非常完整的线性DP及记忆化搜索讲义

    基础概念 我们之前的课程当中接触了最基础的动态规划. 动态规划最重要的就是找到一个状态和状态转移方程. 除此之外,动态规划问题分析中还有一些重要性质,如:重叠子问题.最优子结构.无后效性等. 最优子结 ...

  6. 洛谷P1140 相似基因(线性DP)

    题目背景 大家都知道,基因可以看作一个碱基对序列.它包含了444种核苷酸,简记作A,C,G,TA,C,G,TA,C,G,T.生物学家正致力于寻找人类基因的功能,以利用于诊断疾病和发明药物. 在一个人类 ...

  7. POJ-2346 Lucky tickets(线性DP)

    Lucky tickets Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 3298 Accepted: 2174 Descrip ...

  8. CH5102 Mobile Service【线性dp】

    5102 Mobile Service 0x50「动态规划」例题 描述 一个公司有三个移动服务员,最初分别在位置1,2,3处.如果某个位置(用一个整数表示)有一个请求,那么公司必须指派某名员工赶到那个 ...

  9. Nowcoder Removal ( 字符串上的线性 DP )

    题目链接 题意 : 给出长度为 n 的字符串.问你准确删除 m 个元素之后.能产生多少种不同的子串 分析 ( 参考博客 ):  可以考虑线性 DP 解决这个问题 试着如下定义动态规划数组 dp[i][ ...

  10. P3387缩点(tarjan+拓扑排序+线性dp)

    题目描述 给定一个 n个点 m 条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大.你只需要求出这个权值和. 允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次. 输入 ...

随机推荐

  1. 一文搞懂mysql索引底层逻辑,干货满满!

    一.什么是索引 在mysql中,索引是一种特殊的数据库结构,由数据表中的一列或多列组合而成,可以用来快速查询数据表中有某一特定值的记录.通过索引,查询数据时不用读完记录的所有信息,而只是查询索引列即可 ...

  2. 从 Hadoop 到云原生, 大数据平台如何做存算分离

    Hadoop 的诞生改变了企业对数据的存储.处理和分析的过程,加速了大数据的发展,受到广泛的应用,给整个行业带来了变革意义的改变:随着云计算时代的到来, 存算分离的架构受到青睐,企业开开始对 Hado ...

  3. 【debug】 Linux中top的使用

    在我们日常的开发中,我们经常需要查看每个线程的cpu使用情况.其实,在linux中,top也是我们查看cpu使用状况的一个好帮手 top:先查看每一个进程的使用状况 我们可以发现PID:3800这个经 ...

  4. .Net7 内容汇总(1)

    .Net7 RC1发布 在9月14号,.Net7 RC1正式发布了. 按照微软的说法 This is the first of two release candidates (RC) for .NET ...

  5. MySQL读写分离之——ProxySQL

    文章转载自:https://blog.csdn.net/u012280685/article/details/113520692?spm=1001.2014.3001.5501 实现一个简单的读写分离 ...

  6. 运用Filebeat module分析nginx日志

    在同一台主机上事先安装好filebeat,elasticsearch和kibana filebeat配置 安装完Filebeat后,可以看到在Filebeat的安装目录下有一个叫做filebeat.y ...

  7. 官方使用logstash同步Mysql数据表到ES的摘抄

    官方文档地址:https://www.elastic.co/guide/en/logstash/current/plugins-inputs-jdbc.html#plugins-inputs-jdbc ...

  8. linux系统排查数据包常用命令

    1.查看当前系统中生效的所有参数 sysctl -a 2.统计处于TIME_WAIT状态的TCP连接数 netstat -ant|grep TIME_WAIT|wc -l 3.统计TCP连接数 net ...

  9. MySQL集群搭建(3)-MMM高可用架构

    1 MMM 介绍 1.1 简介 MMM 是一套支持双主故障切换以及双主日常管理的第三方软件.MMM 由 Perl 开发,用来管理和监控双主复制,虽然是双主架构,但是业务上同一时间只允许一个节点进行写入 ...

  10. 6.监控elasticsearch集群---放弃采用(获取不到数据),建议看另一篇文章:监控elasticsearch

    prometheus监控es,同样采用exporter的方案. 项目地址: elasticsearch_exporter:https://github.com/justwatchcom/elastic ...