【刷题版】掌握算法的一揽子计划——动态规划总结

简介: 【刷题版】掌握算法的一揽子计划——动态规划总结

动态规划是一种通过将原问题分解为相对简单的子问题来求解,然后将子问题的解存储起来避免之后重复计算,并最终将子问题组合成原问题的解决方法。动态规划并不算是一种具体的算法,更应该被认为是一种解决问题的思想。


动态规划通常适用于具有重叠子问题和最优子结构性质的问题。具有重叠子问题的问题意味着在解决问题的过程中,我们需要解决相同的子问题。而最优子结构性质意味着问题的最优解可以通过其子问题的最优解来计算。


能用动态规划解决的问题,需要满足三个条件:最优子结构,无后效性和子问题重叠。


最优子结构:假设问题的最优解所包括的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。

无后效性:即某阶段状态一旦确定。就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响曾经的状态。仅仅与当前状态有关。

子问题重叠:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到(该性质并非动态规划适用的必要条件,可是假设没有这条性质。动态规划算法同其它算法相比就不具备优势)。

动态规划算法的具体实现方法有多种,例如自顶向下的记忆化搜索和自底向上的迭代算法。在实际应用中,动态规划可以用来解决一些经典问题,如最长公共子序列问题、背包问题和图的最短路径问题等。


本文主要举一些简单和常见的例子。


线性 DP


线性DP是动态规划问题中的一类问题,指状态之间有线性关系的动态规划问题。也可以是多维的线性问题。


常见问题:


数字金字塔

最长上升子序列

最长不下降子序列


与上一题相比,需要输出一个符合条件的解,可以用一个额外的数组记录以每一个元素结尾时该元素所对应的最长不下降子序列的长度,然后逆序遍历每一个元素,记录符合条件的数据。浅浅贴一个代码 ↓ \downarrow↓

#include <iostream>
using namespace std;
const int N = 200 + 5;
// dp 保存第 i 个数字结尾的子序列的长度
// sc 保存原始数据
// tp 保存临时序列
int dp[N], sc[N], tp[N];
// 从 s 到 e 搜索 第一个 大于 tar 的元素的下标
int bin_search(const int * nums, int s, int e, int tar) {
    if (s > e) return 0;
    int l = s, r = e;
    while (l <= r) {
        int mid = (l + r) >> 1;
        if (nums[mid] <= tar)
            l = mid + 1;
        else
            r = mid - 1;
    }
    return l;
}
int main() {
    int n;
    cin >> n;
    int idx = 0;
    for (int i = 0; i < n; ++i) {
        scanf("%d", &sc[i]);
        int k = bin_search(tp, 0, idx - 1, sc[i]);
        if (k == idx) {
            tp[idx++] = sc[i];
        } else {
            tp[k] = sc[i];
        }
        dp[i] = k + 1;
    }
    cout << "max=" << idx << endl;
    int ans[idx + 5], len = idx;
    for (int i = n - 1; i >= 0; --i) {
        if (dp[i] == len)
            ans[len--] = sc[i];
    }
    for (int i = 1; i <= idx; ++i) {
        printf("%d ", ans[i]);
    }
}


一个应用:导弹拦截

     在这个问题中,第一问求,最多可以拦截的导弹数,按题意,每一发炮弹都不能高于前一发的高度,此处只需要求一个最长不上升子序列就好了

     第二问中,求最少需要多少套设备,正常想最少需要,那么我们可以每套设备尽可能拦截多的导弹,直至全部拦截完为止,但若求多次最长不上升子序列很麻烦。这有一个 trick 是,我们可以发现按最初的求法,每次求出的序列和之前的序列一定会有一个上升的过程,不然就可以归到前一个子序列里边了。所以第二问的解就是同时求一个最长严格上升子序列即可。

最长公共子序列

最大子序和

最大上升子序和

编辑距离


背包问题


背包问题(Knapsack problem)是一种组合优化的NP完全问题。问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。这个问题直接看背包九讲就好了


常见问题:


01背包

完全背包

混合背包

分组背包

货币系统

      相当于 m 大小的背包,n 种物品

数字组合

装箱问题

     将体积也看成价值也就是求可以装下的最大价值


参考资料


https://oi-wiki.org/dp

https://blog.csdn.net/qq_38670588/article/details/108186884

https://baike.baidu.com/item/%E8%83%8C%E5%8C%85%E9%97%AE%E9%A2%98/2416931?fr=aladdin


相关文章
|
2月前
|
存储 算法
深入了解动态规划算法
深入了解动态规划算法
62 1
|
2月前
|
算法 测试技术 C++
【动态规划算法】蓝桥杯填充问题(C/C++)
【动态规划算法】蓝桥杯填充问题(C/C++)
|
5月前
|
算法 开发者 Python
惊呆了!Python算法设计与分析,分治法、贪心、动态规划...这些你都会了吗?不会?那还不快来学!
【7月更文挑战第10天】探索编程巅峰,算法至关重要。Python以其易读性成为学习算法的首选。分治法,如归并排序,将大问题拆解;贪心算法,如找零问题,每步求局部最优;动态规划,如斐波那契数列,利用子问题解。通过示例代码,理解并掌握这些算法,提升编程技能,面对挑战更加从容。动手实践,体验算法的神奇力量吧!
74 8
|
28天前
|
算法 Python
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果;贪心算法在每一步选择局部最优解,追求全局最优;动态规划通过保存子问题的解,避免重复计算,确保全局最优。这三种算法各具特色,适用于不同类型的问题,合理选择能显著提升编程效率。
40 2
|
2月前
|
算法
动态规划算法学习三:0-1背包问题
这篇文章是关于0-1背包问题的动态规划算法详解,包括问题描述、解决步骤、最优子结构性质、状态表示和递推方程、算法设计与分析、计算最优值、算法实现以及对算法缺点的思考。
80 2
动态规划算法学习三:0-1背包问题
|
2月前
|
算法
动态规划算法学习四:最大上升子序列问题(LIS:Longest Increasing Subsequence)
这篇文章介绍了动态规划算法中解决最大上升子序列问题(LIS)的方法,包括问题的描述、动态规划的步骤、状态表示、递推方程、计算最优值以及优化方法,如非动态规划的二分法。
76 0
动态规划算法学习四:最大上升子序列问题(LIS:Longest Increasing Subsequence)
|
2月前
|
算法
动态规划算法学习二:最长公共子序列
这篇文章介绍了如何使用动态规划算法解决最长公共子序列(LCS)问题,包括问题描述、最优子结构性质、状态表示、状态递归方程、计算最优值的方法,以及具体的代码实现。
156 0
动态规划算法学习二:最长公共子序列
|
2月前
|
存储 人工智能 算法
【算法——动态规划】蓝桥ALGO-1007 印章(C/C++)
【算法——动态规划】蓝桥ALGO-1007 印章(C/C++)
【算法——动态规划】蓝桥ALGO-1007 印章(C/C++)
|
2月前
|
存储 算法
动态规划算法学习一:DP的重要知识点、矩阵连乘算法
这篇文章是关于动态规划算法中矩阵连乘问题的详解,包括问题描述、最优子结构、重叠子问题、递归方法、备忘录方法和动态规划算法设计的步骤。
122 0
|
2月前
|
数据可视化 搜索推荐 Python
Leecode 刷题笔记之可视化六大排序算法:冒泡、快速、归并、插入、选择、桶排序
这篇文章是关于LeetCode刷题笔记,主要介绍了六大排序算法(冒泡、快速、归并、插入、选择、桶排序)的Python实现及其可视化过程。
17 0