动态规划篇——线性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. 当 SQL DELETE 邂逅 Table aliases,会擦出怎样的火花

    开心一刻 晚上,女儿眼噙泪水躺在床上 女儿:你口口声声说爱我,说陪我,却天天想着骗我零花钱,你是我亲爹吗? 我:你想知道真相 女儿:想! 我:那你先给爸爸两百块钱! 环境准备 MySQL 不同版本 利 ...

  2. 输入法词库解析(一)百度自定义方案.def

    详细代码:https://github.com/cxcn/dtool 前言 .def 是百度手机输入法-更多设置-自定义输入方案所使用的格式. 解析 码表偏移量 0x6D # 占用字节数 描述 a 1 ...

  3. 面试突击84:Spring 有几种事务隔离级别?

    Spring 中的事务隔离级别和数据库中的事务隔离级别稍有不同,以 MySQL 为例,MySQL 的 InnoDB 引擎中的事务隔离级别有 4 种,而 Spring 中却包含了 5 种事务隔离级别. ...

  4. 生产环境中使用Kibana

    在 Kibana 中使用 X-Pack 使用 X-Pack 安全模块 控制用户通过 Kibana 可以访问哪些 Elasticsearch 数据. 当安装 X-Pack 时,Kibana 用户必须登陆 ...

  5. mongodb集群搭建(分片+副本)开启安全认证

    关于安全认证得总结: 这个讲述的步骤也是先创建超管用户,关闭服务,然后生成密钥文件,开启安全认证,启动服务 相关概念 先来看一张图: 从图中可以看到有四个组件:mongos.config server ...

  6. 利用 Nginx 反向代理搭建本地 yum 服务器

    在政府,医院等单位有网络安全要求,对内外网进行物理隔离,然而内网主机无法访问互联网下载安装包,通过Nginx 反向代理搭建本地yum服务器实现内网主机安装包下载. Centos 8.2 部署 Ngin ...

  7. 在 CentOS 8/RHEL 8 上安装和使用 Cockpit

    Cockpit 是一个基于 Web 的服务器管理工具,可用于 CentOS 和 RHEL 系统.最近发布的 CentOS 8 和 RHEL 8,其中 cockpit 是默认的服务器管理工具.它的软件包 ...

  8. Python(二)常用的正则表达式

    表单字段验证常用正则表达式 1.姓名 /^[\u4e00-\u9fa5]{2,4}$/ //2-41 2.手机号码 /^(13[0-9]|14[01456879]|15[0-35-9] 3.座机号码 ...

  9. Java连接MySQL数据库。编写一个应用程序,在主类Test_4类中,通过JDBC访问stu数据库,显示t_student表中的内容(表结构见表1),显示效果自己设计。

    题目2:编写一个应用程序,在主类Test_4类中,通过JDBC访问stu数据库,显示t_student表中的内容(表结构见表1),显示效果自己设计.之后,可根据显示的内容进行某条记录的删除(以id为条 ...

  10. [CG从零开始] 5. 搞清 MVP 矩阵理论 + 实践

    在 4 中成功绘制了三角形以后,下面我们来加载一个 fbx 文件,然后构建 MVP 变换(model-view-projection).简单介绍一下: 从我们拿到模型(主要是网格信息)文件开始,模型网 ...