基础算法学习——动态规划篇

本文介绍了动态规划的基本概念,包括什么是动态规划、重叠子问题的识别与解决方案、状态转移方程的定义,以及状态压缩的应用实例,如斐波那契数列。作者分享了解决动态规划问题的思维框架,强调了状态转移方程在动态规划中的关键作用。
摘要由CSDN通过智能技术生成

基础算法学习——动态规划篇


提示:本文随时更新,以记录对于该类型算法的学习过程,作者水平有限,所有内容仅为我个人一孔之见,如果大家觉得有用欢迎点赞收藏。

一.动态规划是什么

  • 动态规划问题的一般形式就是求最值。动态规划其实是运筹学的一种最优化方法,只不过在计算机问题上应用比较多,比如说让你求最长递增子序列呀,最小编辑距离呀等等。
    • 既然是要求最值,核心问题是什么呢?求解动态规划的核心问题是穷举。因为要求最值,肯定要把所有可行的答案穷举出来,然后在其中找最值呗。
    • 动态规划的穷举有点特别,因为这类问题存在「重叠子问题」,如果暴力穷举的话效率会极其低下,所以需要「备忘录」或者「DP table」来优化穷举过程,避免不必要的计算。(空间换时间)
    • 动态规划问题一定会具备「最优子结构」,才能通过子问题的最值得到原问题的最值。
    • 虽然动态规划的核心思想就是穷举求最值,但是问题可以千变万化,穷举所有可行解其实并不是一件容易的事,只有列出正确的「状态转移方程才能正确地穷举。
  • 以上提到的重叠子问题、最优子结构、状态转移方程就是动态规划三要素。具体什么意思等会会举例详解,但是在实际的算法问题中,写出状态转移方程是最困难的,这也就是为什么很多朋友觉得动态规划问题困难的原因,我来提供我研究出来的一个思维框架,辅助你思考状态转移方程:
明确 base case -> 明确「状态」-> 明确「选择」 -> 定义 dp 数组/函数的含义。
# 初始化 base case
dp[0][0][...] = base
# 进行状态转移
for 状态1 in 状态1的所有取值:
    for 状态2 in 状态2的所有取值:
        for ...
            dp[状态1][状态2][...] = 求最值(选择1,选择2...)

二.什么是重叠子问题以及如何解决它

  • 什么是重叠子问题:例如计算斐波拉契数列会有很多重复子问题存在(例如在各个递归中计算很多次f(3))
  • 如何解决重叠子问题:一般使用一个数组充当「备忘录」,当然你也可以使用哈希表(字典),思想都是一样的(除第一次计算外,之后需要使用,查备忘录即可)
    在这里插入图片描述
  • 上图对相同的结点进行剪枝操作(用备忘录存储)
    • 计算f(20)的斐波拉契问题只需要计算20个子问题即可(用长20的备忘录存储)(自顶向下)
int fib(int N) {
    if (N < 1) return 0;
    // 备忘录全初始化为 0
    vector<int> memo(N + 1, 0);
    // 进行带备忘录的递归
    return helper(memo, N);
}
 
int helper(vector<int>& memo, int n) {
    // base case 
    if (n == 1 || n == 2) return 1;
    // 已经计算过
    if (memo[n] != 0) return memo[n];
    memo[n] = helper(memo, n - 1) + helper(memo, n - 2);
    return memo[n];
}
  • dp 数组的迭代解法(自底向上)
int fib(int N) {
    vector<int> dp(N + 1, 0);
    // base case
    dp[1] = dp[2] = 1;
    for (int i = 3; i <= N; i++)
        dp[i] = dp[i - 1] + dp[i - 2];
    return dp[N];
}

三.什么是状态转移方程

  • 实际上就是描述问题结构的数学形式:
    在这里插入图片描述

四.什么是状态压缩

  • 例如:根据斐波那契数列的状态转移方程,当前状态只和之前的两个状态有关,其实并不需要那么长的一个 DP table 来存储所有的状态,只要想办法存储之前的两个状态就行了。
//状态压缩下的斐波拉契算法实现code
class Solution {
public:
    int fib(int n) {
        if(n == 0){
            return 0;
        }
        if(n == 2 || n == 1){
            return 1;
        }
        int prev = 1,curr = 1;
        for(int i = 3;i <= n;i++){
            int sum = prev + curr;
            prev = curr;
            curr = sum;
        }
        return curr;
    }
};

五.什么是最优子结构

  • 待补充

六.参考文章内容以及其链接

  • 20
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值