动态规划篇——线性DP
动态规划篇——线性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的更多相关文章
- 动态规划_线性dp
https://www.cnblogs.com/31415926535x/p/10415694.html 线性dp是很基础的一种动态规划,,经典题和他的变种有很多,比如两个串的LCS,LIS,最大子序 ...
- 【线性DP】数字三角形
题目链接 原题链接 题目描述 给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大. 7 3 ...
- 动态规划——线性dp
我们在解决一些线性区间上的最优化问题的时候,往往也能够利用到动态规划的思想,这种问题可以叫做线性dp.在这篇文章中,我们将讨论有关线性dp的一些问题. 在有关线性dp问题中,有着几个比较经典而基础的模 ...
- 线性DP总结(LIS,LCS,LCIS,最长子段和)
做了一段时间的线性dp的题目是时候做一个总结 线性动态规划无非就是在一个数组上搞嘛, 首先看一个最简单的问题: 一,最长字段和 下面为状态转移方程 for(int i=2;i<=n;i++) { ...
- 非常完整的线性DP及记忆化搜索讲义
基础概念 我们之前的课程当中接触了最基础的动态规划. 动态规划最重要的就是找到一个状态和状态转移方程. 除此之外,动态规划问题分析中还有一些重要性质,如:重叠子问题.最优子结构.无后效性等. 最优子结 ...
- 洛谷P1140 相似基因(线性DP)
题目背景 大家都知道,基因可以看作一个碱基对序列.它包含了444种核苷酸,简记作A,C,G,TA,C,G,TA,C,G,T.生物学家正致力于寻找人类基因的功能,以利用于诊断疾病和发明药物. 在一个人类 ...
- POJ-2346 Lucky tickets(线性DP)
Lucky tickets Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 3298 Accepted: 2174 Descrip ...
- CH5102 Mobile Service【线性dp】
5102 Mobile Service 0x50「动态规划」例题 描述 一个公司有三个移动服务员,最初分别在位置1,2,3处.如果某个位置(用一个整数表示)有一个请求,那么公司必须指派某名员工赶到那个 ...
- Nowcoder Removal ( 字符串上的线性 DP )
题目链接 题意 : 给出长度为 n 的字符串.问你准确删除 m 个元素之后.能产生多少种不同的子串 分析 ( 参考博客 ): 可以考虑线性 DP 解决这个问题 试着如下定义动态规划数组 dp[i][ ...
- P3387缩点(tarjan+拓扑排序+线性dp)
题目描述 给定一个 n个点 m 条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大.你只需要求出这个权值和. 允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次. 输入 ...
随机推荐
- 人脸识别、活体检测(眨眼、摇头、张嘴动作)clmtrackr
人脸识别.活体检测(眨眼.摇头.张嘴动作)项目总结 项目需求 / 步骤实现描述: 1.申请摄像头权限,开始识别面部信息.同时开始录像 : 2.随机顺序生成面部检验动作: 3.并开始倒计时,需10s内完 ...
- Kubernetes 多租户:多租户介绍
多租户集群由多个用户和/或工作负载共享,这些用户和/或工作负载被称为"租户".多租户集群的运营方必须将租户彼此隔离,以最大限度地减少被盗用的租户或恶意租户可能对集群和其他租户造成的 ...
- Elastic Stack 8.0 再次创建enrollment token
enrollment token 在第一个 Elasticsearch 启动后的有效时间为30分钟.超过30分钟的时间上述 token 将会无效. enrollment token分两个,一个是kib ...
- Kibana探索数据(Discover)
总结说明: 1.先在Management/Kibana/Index Patterns 界面下添加索引模式(前提是有索引数据) 2.在Discover界面选中响应的索引模式 3.开启Kibana 查询语 ...
- 【可视化大屏教程】用Python开发智慧城市数据分析大屏!
目录 一.开发背景 二.讲解代码 2.1 大标题+背景图 2.2 各区县交通事故统计图-系列柱形图 2.3 图书馆建设率-水球图 2.4 当年城市空气质量aqi指数-面积图 2.5 近7年人均生产总值 ...
- Jhipster自动生成实体类等文件
官网:https://www.jhipster.tech/cn/ 准备工作 安装node(npm) 准备jdl文件 安装Jhipster:npm install -g generator-jhipst ...
- KNN算法介绍及源码实现
一.KNN算法介绍 邻近算法,或者说K最邻近(KNN,K-NearestNeighbor)分类算法是数据挖掘分类技术中最简单的方法之一.所谓K最近邻,就是K个最近的邻居的意思,说的是每个样本都可以用它 ...
- 新零售SaaS架构:中央库存系统架构设计
近年来,越来越多的零售企业大力发展全渠道业务.在销售额增长上,通过线上的小程序.直播.平台渠道等方式,拓展流量变现渠道.在会员增长方面,通过多样的互动方式,全渠道触达消费者,扩大会员规模.而全渠道的库 ...
- JavaScript事件驱动
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- LOJ2324「清华集训 2017」小Y和二叉树
题目链接 瞎jb贪一发就过了.首先度数<=2且编号最小的点一定是中序遍历最靠前的点,我们从这个点开始dfs一遍算出子树中度数<=2且编号最小的点记为\(f(i)\),然后从这个点开始一步一 ...