算法基础复盘笔记Day10【动态规划】—— 线性DP

这篇具有很好参考价值的文章主要介绍了算法基础复盘笔记Day10【动态规划】—— 线性DP。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

❤ 作者主页:欢迎来到我的技术博客😎
❀ 个人介绍:大家好,本人热衷于Java后端开发,欢迎来交流学习哦!( ̄▽ ̄)~*
🍊 如果文章对您有帮助,记得关注点赞收藏评论⭐️⭐️⭐️
📣 您的支持将是我创作的动力,让我们一起加油进步吧!!!🎉🎉

第一章 线性DP

一、数字三角形

1. 题目描述

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

        7
      3   8
    8   1   0
  2   7   4   4
4   5   2   6   5

输入格式

第一行包含整数 n,表示数字三角形的层数。

接下来 n 行,每行包含若干整数,其中第 i i i 行表示数字三角形第 i i i 层包含的整数。

输出格式

输出一个整数,表示最大的路径数字和。

数据范围

1 ≤ n ≤ 500 1≤n≤500 1n500,
− 10000 ≤ 三角形中的整数 ≤ 10000 −10000≤三角形中的整数≤10000 10000三角形中的整数10000

输入样例:

5
7
3 8
8 1 0 
2 7 4 4
4 5 2 6 5

输出样例:

30

2. 思路分析

状态表示:f[i][j] 表示从 ( 1 , 1 ) (1,1) (1,1) ( i , j ) (i, j) (i,j) 的所有路径的集合。

状态计算:

  • ( i , j ) (i,j) (i,j) 这个点可以从 ( i − 1 , j ) (i -1, j) (i1,j) 走过来,即 f[i][j] = f[i - 1][j] + a[i][j]
  • ( i , j ) (i,j) (i,j) 这个点可以从 ( i − 1 , j − 1 ) (i -1, j - 1) (i1,j1) 走过来,即 f[i][j] = f[i - 1][j - 1] + a[i][j]
  • 因此状态状态方程为:f[i][j] = max(f[i - 1][j] + a[i][j], f[i - 1][j - 1] + a[i][j])

最后,枚举最下面一层,返回最大的 f[n][i]即可。


3. 代码实现

#include <bits/stdc++.h>

using namespace std;

const int N  = 510, INF = 1e9;

int n;
int a[N][N];
int f[N][N]; //f[i][j]表示从(1,1)走到(i,j)的所有路径中,总和最大的那一条路径的总和

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++)
        for (int j = 1; j <= i; j ++)
            cin >> a[i][j];
    
    //初始化,对于边界点只有一条路径通向它
    for (int i = 0; i <= n; i ++)
        for (int j = 0; j <= i + 1; j ++)
            f[i][j] = -INF; //初始化为负无穷
    
    f[1][1] = a[1][1]; //由f[i][j]的定义,(1,1)点的f值就是本身
    for (int i = 2; i <= n; i ++) //这样,我们从第二层枚举至第n层
        for (int j = 1; j <= i; j ++) 
            f[i][j] = max(f[i - 1][j] + a[i][j], f[i - 1][j - 1] + a[i][j]);
    
    int res = -INF;
    for (int i = 1; i <= n; i ++) res = max(res, f[n][i]); //最大值在第n层的某一个点处取得
    
    cout << res << endl;
    
    return 0;
}

二、最长上升子序列

1. 题目描述

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

输入格式

第一行包含整数 N。

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

输出格式

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

数据范围

1 ≤ N ≤ 1000 1≤N≤1000 1N1000
− 1 0 9 ≤ 数列中的数 ≤ 1 0 9 −10^9≤数列中的数≤10^9 109数列中的数109

输入样例:

7
3 1 2 1 8 5 6

输出样例:

4

2. 思路分析

状态表示:f[i] 表示 a[i] 为结尾 的最长上升子序列的长度。

初始条件:f[i] = 1

状态转移方程: i i i从第一个数开始枚举,1 ≤ \leq j j j < \lt < i i i,如果 a[j] < a[i],则 f[i] = max(f[i], f[j] + 1)


3. 代码实现

#include <bits/stdc++.h>

using namespace std;

const int N = 1010;

int n;
int a[N], f[N];

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> a[i];;
    
    for (int i = 1; i <= n; i ++)
    {
        f[i] = 1; //初始化,设f[i]默认为1,找不到前面数字小于自己的时候就为1
        for (int j = 1; j < i; j ++)
            if (a[j] < a[i])
                f[i] = max(f[i], f[j] + 1);
    }
    
    int res = 0;
    for (int i = 1; i <= n; i ++) res = max(res, f[i]); //取最大值
    
    cout << res << endl;
    
    return 0;
}

三、最长上升子序列 II

1. 题目描述

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

输入格式

第一行包含整数 N。

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

输出格式

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

数据范围

1 ≤ N ≤ 100000 1≤N≤100000 1N100000
− 1 0 9 ≤ 数列中的数 ≤ 1 0 9 −10^9≤数列中的数≤10^9 109数列中的数109

输入样例:

7
3 1 2 1 8 5 6

输出样例:

4

2. 思路分析

最长上升子序列这道题的时间复杂度是 O ( n 2 ) O(n^2) O(n2),用在这道题会超时。

如果把内层循环改为 二分查找,就能把内存查找时间降为 l o g n logn logn,则时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
但是,二分查找的前提是有序序列,故增加一个 b b b 数组,用来记录上升子序列。
关键问题:动态更新 b b b 数组

考虑新进来一个元素 a [ i ] a[i] a[i]

  • 大则添加:如果 a[i] > b[len],直接让 b[++ len] = a[i]。即 b b b 数组的长度增加1,并且添加了一个元素;
  • 小则替换:如果 a[i] <= b[len],就用 a[i] 替换 b b b数组中第一个大于或等于 a[i] 的元素。假设第一个大于等于 a[i] 的元素是b[j],那么用 a[i] 换掉 b[j] ,会使得 b[1....j] 这个上升子序列的结尾元素更小。对于一个上升子序列,其结尾元素越小,越有利于接上其他元素,也就越可能变得更长。

模拟过程:
算法基础复盘笔记Day10【动态规划】—— 线性DP


3. 代码实现

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

int n;
int a[N], b[N]; //b数组记录上升子序列
int len; //上升子序列的长度

//二分查找第一个大于等于x的位置
int find(int x)
{
    int l = 1, r = len;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (b[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return l;
}

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> a[i];
    
    b[++ len] = a[1];
    //动态更新b数组
    for (int i = 2; i <= n; i ++)
        if (a[i] > b[len])  //大于则添加
        {
            b[++ len] = a[i];
        }
        else //小于等于则替换
        {
            int tmp = find(a[i]); 
            b[tmp] = a[i];
        }
    
    cout << len << endl;
    
    return 0;
}

四、最长公共子序列

1. 题目描述

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

输入格式

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

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

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

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

输出格式

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

数据范围

1 ≤ N , M ≤ 10001 ≤ 1≤N,M≤10001≤ 1N,M10001

输入样例:

4 5
acbd
abedc

输出样例:

3

2. 思路分析

状态表示:f[i][j] 记录序列 a[1...i]b[1...j]的最长公共序列长度。

考虑末尾元素 a[i]b[j]是否在公共子序列中:

  • a [ i ] = b [ j ] a[i] = b[j] a[i]=b[j],则 a [ i ] a[i] a[i] b [ j ] b[j] b[j]在公共子序列中,即 f[i][j] = f[i - 1][j - 1] + 1
  • a [ i ] ≠ b [ j ] a[i] \neq b[j] a[i]=b[j],且 a [ i ] a[i] a[i]不在公共子序列中,则可去掉 a [ i ] a[i] a[i],即 f[i][j] = f[i - 1][j]
  • a [ i ] ≠ b [ j ] a[i] \neq b[j] a[i]=b[j],且 b [ j ] b[j] b[j]不在公共子序列中,则可去掉 b [ j ] b[j] b[j],即 f[i][j] = f[1][j - 1]

状态转移方程:

  • f[i][j] = f[i - 1][j - 1] + 1 a [ i ] = b [ j ] a[i] = b[j] a[i]=b[j]
  • f[i][j] = max(f[i - 1][j], f[i][j - 1]) a [ i ] ≠ b [ j ] a[i] \neq b[j] a[i]=b[j]

3. 代码实现

#include <bits/stdc++.h>

using namespace std;

const int N = 1010;

int n, m;
char a[N], b[N];
int f[N][N];

int main()
{
    cin >> n >> m;
    cin >> a + 1 >> b + 1; //下标从1开始
    
    for (int i = 1; i <= n; i ++)
        for (int j = 1; j <= m; j ++)
        {
            f[i][j] = max(f[i - 1][j], f[i][j - 1]);
            if (a[i] == b[j]) f[i][j] = max(f[i][j], f[i - 1][j - 1] + 1);
        }
        
    cout << f[n][m] << endl;
    
    return 0;
}

五、最短编辑距离

1. 题目描述

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

  1. 删除–将字符串 A 中的某个字符删除。
  2. 插入–在字符串 A 的某个位置插入某个字符。
  3. 替换–将字符串 A 中的某个字符替换为另一个字符。

现在请你求出,将 A 变为 B 至少需要进行多少次操作。

输入格式

第一行包含整数 n,表示字符串 A 的长度。

第二行包含一个长度为 n 的字符串 A。

第三行包含整数 m,表示字符串 B 的长度。

第四行包含一个长度为 m 的字符串 B。

字符串中均只包含大小写字母。

输出格式

输出一个整数,表示最少操作次数。

数据范围

1 ≤ n , m ≤ 1000 1≤n,m≤1000 1n,m1000

输入样例:

10 
AGTCTGACGC
11 
AGTAAGTAGGC

输出样例:

4

2. 思路分析

状态表示: f[i][j] 表示从 a[1...i]b[1...j]的编辑距离。

  • a [ i ] = b [ j ] a[i] = b[j] a[i]=b[j],即 f[i][j] = f[i - 1][j - 1]

  • a [ i ] ≠ b [ j ] a[i] \neq b[j] a[i]=b[j],则需要考察修改、插入和删除的编辑距离的最小值:

    • 修改操作:a[1...i-1] == b[1...j-1],把 a [ i ] a[i] a[i] 改为 b [ j ] b[j] b[j],即 f[i][j] = f[i - 1][j - 1] + 1
    • 插入操作:a[1...i] == b[1...j-1],在 a [ i ] a[i] a[i] 后插入 b [ j ] b[j] b[j],即 f[i][j] = f[i][j - 1] + 1
    • 删除操作:a[1...i - 1] == b[1...j],删除 a [ i ] a[i] a[i],即 f[i][j] = f[i - 1][j] + 1

状态转移方程:

  • f[i][j] = f[i - 1][j - 1] a [ i ] = b [ j ] a[i] = b[j] a[i]=b[j]
  • f[i][j] = min(f[i - 1][j - 1],f[i - 1][j], f[i][j - 1]) + 1 a [ i ] ≠ b [ j ] a[i] \neq b[j] a[i]=b[j]

3. 代码实现

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;

int n, m;
char a[N], b[N];
int f[N][N]; //把a[1~i]变成b[1~j]需要的步数

int main()
{
    cin >> n >> a + 1;
    cin >> m >> b + 1;
    
    //初始化
    for (int i = 0; i <= n; i ++ ) f[i][0] = i; //把a[1~i]变成b[0]需要i步
    for (int i = 0; i <= m; i ++ ) f[0][i] = i; //把a[0]变成b[1~i]需要i步
    
    //因为初始了边界情况,因此直接从1开始
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
        {
            if(a[i] == b[j]) 
                f[i][j] = f[i - 1][j - 1];
            else
                f[i][j] = min(f[i - 1][j - 1] + 1, min(f[i - 1][j] + 1 , f[i][j - 1] + 1));
        }

    printf("%d\n", f[n][m]);

    return 0;
}

六、编辑距离

1. 题目描述

给定 n 个长度不超过 10 的字符串以及 m 次询问,每次询问给出一个字符串和一个操作次数上限。

对于每次询问,请你求出给定的 n 个字符串中有多少个字符串可以在上限操作次数内经过操作变成询问给出的字符串。

每个对字符串进行的单个字符的插入、删除或替换算作一次操作。

输入格式

第一行包含两个整数 n 和 m。

接下来 n 行,每行包含一个字符串,表示给定的字符串。

再接下来 m 行,每行包含一个字符串和一个整数,表示一次询问。

字符串中只包含小写字母,且长度均不超过 10。

输出格式

输出共 m 行,每行输出一个整数作为结果,表示一次询问中满足条件的字符串个数。

数据范围

1 ≤ n , m ≤ 1000 1≤n,m≤1000 1n,m1000

输入样例:

3 2
abc
acd
bcd
ab 1
acbd 2

输出样例:

1
3

2. 思路分析

状态表示: f[i][j] 表示从 a[1...i]b[1...j]的编辑距离。

  • a [ i ] = b [ j ] a[i] = b[j] a[i]=b[j],即 f[i][j] = f[i - 1][j - 1]

  • a [ i ] ≠ b [ j ] a[i] \neq b[j] a[i]=b[j],则需要考察修改、插入和删除的编辑距离的最小值:

    • 修改操作:a[1...i-1] == b[1...j-1],把 a [ i ] a[i] a[i] 改为 b [ j ] b[j] b[j],即 f[i][j] = f[i - 1][j - 1] + 1
    • 插入操作:a[1...i] == b[1...j-1],在 a [ i ] a[i] a[i] 后插入 b [ j ] b[j] b[j],即 f[i][j] = f[i][j - 1] + 1
    • 删除操作:a[1...i - 1] == b[1...j],删除 a [ i ] a[i] a[i],即 f[i][j] = f[i - 1][j] + 1

状态转移方程:

  • f[i][j] = f[i - 1][j - 1] a [ i ] = b [ j ] a[i] = b[j] a[i]=b[j]
  • f[i][j] = min(f[i - 1][j - 1],f[i - 1][j], f[i][j - 1]) + 1 a [ i ] ≠ b [ j ] a[i] \neq b[j] a[i]=b[j]

3. 代码实现

#include <bits/stdc++.h>

using namespace std;

const int N = 15, M = 1010;

int n, m;
int f[N][N];
char str[M][N]; //存储给定的n个字符串

//字符串a变为字符串b的最短编辑距离
int edit_distance(char a[], char b[])
{
    int la = strlen(a + 1), lb = strlen(b + 1);
    
    //初始化
    for (int i = 0; i <= lb; i ++) f[0][i] = i;
    for (int i = 0; i <= la; i ++) f[i][0] = i;
    
    for (int i = 1; i <= la; i ++)
        for (int j = 1; j <= lb; j ++)
        {
            f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1);
            f[i][j] = min(f[i][j], f[i - 1][j - 1] + (a[i] != b[j]));
        }
    
    return f[la][lb];
}

int main()
{
    cin >> n >> m;
    for (int i = 0; i < n; i ++) cin >> (str[i] + 1);
    
    while (m --)
    {
        char s[N]; //存储给定查询的字符串
        int limit;
        cin >> (s + 1) >> limit;
        
        int res = 0;
        for (int i = 0; i < n; i ++) //每次枚举所有给定的字符串
            if (edit_distance(str[i], s) <= limit)
                res ++;
        
        cout << res << endl;
    }
    return 0;
}

创作不易,如果有帮助到你,请给文章点个赞和收藏,让更多的人看到!!!
关注博主不迷路,内容持续更新中。文章来源地址https://www.toymoban.com/news/detail-420507.html

到了这里,关于算法基础复盘笔记Day10【动态规划】—— 线性DP的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++动态规划-线性dp算法

    莫愁千里路 自有到来风 CSDN 请求进入专栏                                    X 是否进入《 C++ 专栏》? 确定 目录  线性dp简介 斐波那契数列模型  第N个泰波那契数 思路: 代码测试:  三步问题 思路: 代码测试: 最小花费爬楼梯 思路: 代码测试:  路径问题 数字三

    2024年02月19日
    浏览(47)
  • Day36算法记录|动态规划 dp02

    步骤回顾: C语言版本写的很清楚 对应得Java版本视频解析 方法一: 动态规划 1 确定dp数组(dp table)以及下标的含义 dp[i][j] :表示从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径。 2 . 确定递推公式 ,求dp[i][j],只能有两个方向来推导出来,即dp[i - 1][j] 和 dp[i][j - 1]。 3. dp数

    2024年02月12日
    浏览(51)
  • 猿创征文 |【算法面试入门必刷】动态规划-线性dp(四)

    📦个人主页:一二三o-0-O的博客 🏆技术方向:C/C++客户端资深工程师(直播+音视频剪辑) 👨‍💻作者简介:数据结构算法与音视频领域创作者 📒 系列专栏:牛客网面试必刷 📣专栏目标:帮助伙伴们通过系统训练,掌握数据结构与算法,收获心仪Offer 📝推荐一个找工作

    2024年02月02日
    浏览(36)
  • 猿创征文 |【算法面试入门必刷】动态规划-线性dp(一)

    📦个人主页:一二三o-0-O的博客 🏆技术方向:C/C++客户端资深工程师(直播+音视频剪辑) 👨‍💻作者简介:数据结构算法与音视频领域创作者 📒 系列专栏:牛客网面试必刷 📣专栏目标:帮助伙伴们通过系统训练,掌握数据结构与算法,收获心仪Offer 📝推荐一个找工作

    2024年02月03日
    浏览(71)
  • 动态规划(DP)(算法笔记)

    本文内容基于《算法笔记》和官方配套练题网站“晴问算法”,是我作为小白的学习记录,如有错误还请体谅,可以留下您的宝贵意见,不胜感激。 动态规划(Dynamic Programming,DP)是一种用来解决一类最优化问题的算法思想。简单来说,动态规划将一个复杂的问题分解成若干个子

    2024年02月05日
    浏览(46)
  • 动态规划算法刷题笔记【状压dp】

    a1 == 1 判断是否为奇数,如果为1,则为奇数 因为奇数二进制末位一定是1,所以 与1 得到的结果是1 这里,114——2 14 ——第15位是1,可以表示14个1 i(1j)—— 从0开始是因为,原本第1位就是1。所以j=0时,对应的就是 i 的最低位 F l o y d Floyd Fl oy d 算法: n o w ∣ f l a g = = f l

    2024年02月11日
    浏览(42)
  • acwing算法基础之动态规划--DP习题课1

    暂无。。。 暂无。。。 题目1 :最长严格上升子序列,要求时间复杂度为 O ( n l o g n ) O(nlogn) O ( n l o g n ) 。 解题思路:保存每个长度下的最小的结尾元素值,遍历数组元素时,通过二分找到它,然后更新它即可,返回len。 该算法的关键步骤如下: 定义向量 vec , vec[i] 表示

    2024年02月03日
    浏览(52)
  • 算法竞赛备赛之动态规划训练提升,DP基础掌握

    01背包问题是在M件物品中选择若干件放在空间为W的背包中,每件物品的体积为W1,W2至Wn,价值为P1,P2至Pn,01背包的约束条件是给定几种物品,每种物品有且只有一个,并且有权值和体积两个属性。 01背包问题常常采用动态规划的方法去求解,状态转移方程为:F(W,i)=max{F(W,

    2024年02月08日
    浏览(42)
  • acwing算法基础之动态规划--数位统计DP、状态压缩DP、树形DP和记忆化搜索

    暂无。。。 暂无。。。 题目1 :求a~b中数字0、数字1、…、数字9出现的次数。 思路:先计算1~a中每位数字出现的次数,然后计算1~b-1中每位数字出现的次数,两个相减即是最终答案。 那么,如何计算1~a中每位数字出现的次数呢? 首先,将a的每一位存入向量num中,例如a=123

    2024年02月04日
    浏览(51)
  • 动态规划(DP)入门——线性DP

    在了解线性DP之前,我们首先要知道什么是动态规划,即为将一种复杂问题,分解成很多重叠的子问题,并通过子问题的解得到整个问题的解的算法。听起来比较抽象,动态规划简单来说就是确定问题的状态,通常题目都会提示,一般为“到第i个为止,xx为j(xx为k)的方案数/最

    2024年02月19日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包