动态规划篇——线性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. IK分词器实现原理剖析 —— 一个小问题引发的思考

    前言: 网上很多的文章都建议在使用IK分词器的时候,建立索引的时候使用ik_max_word模式:搜索的时候使用ik_smart模式.理由是max_word模式分词的结果会包含smart分词的结果,这 ...

  2. 基于HBuilderX+UniApp+ThorUI的手机端前端的页面组件化开发经验

    现在的很多程序应用,基本上都是需要多端覆盖,因此基于一个Web API的后端接口,来构建多端应用,如微信.H5.APP.WInForm.BS的Web管理端等都是常见的应用.本篇随笔继续分析总结一下项目 ...

  3. ProxySQL 配置ProxySQL

    转载自:https://www.jianshu.com/p/212397a1be67 假定你已经对ProxySQL的架构有所了解.本文对ProxySQL的所有配置都是使用Admin管理接口完成的,该管 ...

  4. 常量的定义(const和#define)

    定义常量的方法 //均要在调用前(区别全局变量!!) 1.使用#define预处理器 2.使用const关键字 1.#define #define 常量名 常量值 //定义形式,常量名不可以是数字开头 ...

  5. 关于aws-SecurityGroup-安全组策略的批量添加的方法记录

    因一些服务的客户端网络地址段计划变更,会影响到aws上配置这这些网段安全组策略所绑定的资源 因此需要先整理包含了出那些服务的网络地址段的安全组 然后根据旧网段的策略信息,将新的地址段给添加上,待后续正 ...

  6. 从SpringBoot启动,阅读源码设计

    目录 一.背景说明 二.SpringBoot工程 三.应用上下文 四.资源加载 五.应用环境 六.Bean对象 七.Tomcat服务 八.事件模型 九.配置加载 十.数据库集成 十一.参考源码 服务启 ...

  7. spring boot项目使用mybatis-plus代码生成实例

    前言 mybatis-plus官方地址 https://baomidou.com mybatis-plus是mybatis的增强,不对mybatis做任何改变,涵盖了代码生成,自定义ID生成器,快速实 ...

  8. 【Java8新特性】- Stream流

    Java8新特性 - Stream流的应用 生命不息,写作不止 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 一个有梦有戏的人 @怒放吧德德 分享学习心得,欢迎指正,大家一起学习成长! ...

  9. day53-马踏棋盘

    马踏棋盘 1.算法优化的意义 算法是程序的灵魂,为什么有些程序可以在海量数据计算时,依旧保持高速计算? 编程中算法很多,比如八大排序算法(冒泡.选择.插入.快排.归并.希尔.基数.堆排序).查找算法. ...

  10. ESP32 IDF 获取天气信息

    一.注册天气获取账号 我使用的知心天气,没有获取天气账号的小伙伴可以去注册一下,知心天气官网:https://www.seniverse.com/ 取得天气获取的API后,可以直接在浏览器中访问测试一 ...