【算法】——动态规划算法及实践应用

目录

前言:

一、什么是动态规划算法?

[1. 简单介绍](#1. 简单介绍)

[2. 如何实现](#2. 如何实现)

二、实践案例

[1. 第n个泰波那契数](#1. 第n个泰波那契数)

[2. 三步问题](#2. 三步问题)

[3. 解码方法](#3. 解码方法)

总结:


前言:

本篇文章会介绍什么是动态规划算法,并会给出一些实践例题,方便我们快速掌握。

一、什么是动态规划算法?

1. 简单介绍

动态规划的核心思想是:将复杂的大问题拆解成一系列小问题,通过解决并"记住"这些小问题的答案,来避免重复计算,从而高效地解决大问题。

这过程就用到了迭代的思想,即由小推大,一步步接近最终目标。

2. 如何实现

动态规划算法的实现一般有以下几个步骤:

  1. 定义状态表示
  2. 找出状态转移方程(关键步骤)
  3. 初始化
  4. 确定计算顺序
  5. 返回结果

接下我会对这几个步骤进行讲解:

首先动态规划一般会有一个dp表 ,用于存储历史数据,我们先假设这个dp表是一个一维数组。那么第一步定义状态表示就是定义dp i 到底表示了什么 ;第二步就是根据已有数据如果推出下一个值 dp i + 1 比如dp i +1 = dp i + dp i - 1 ;第三步初始化就是确定边界情况,即dp表中前几个数据的值;第四步确定计算顺序就是根据题目具体情况选择从哪边开始计算,再循环遍历;第五步返回dp表中最终的计算结果即可。

二、实践案例

光看概念还是比较抽象,所以我们接下来代入一些例子来进行讲解,让你快速上手动态规划。

1. 第n个泰波那契数

首先从一个简单的题目开始,走一走动态规划的五个步骤:

  • 第一步,定义状态表示,先定义一个dp表,dp i 就表示下标为i的泰波那契数的值
  • 第二步,状态转移方程,本题题目中已经给出,dpn+3 = dpn + dpn+1 + dpn+2
  • 第三步,初始化,由于状态转移方程需要至少三个值,所以前三个泰波那契数都需要进行初始化,分别初始化为0,1,1
  • 第四步,本题的计算顺序是从小推大
  • 第五步,最终返回dpn的值即可

示例代码:

cpp 复制代码
    int tribonacci(int n) {
        // 处理边界情况
        if(n == 0) return 0;
        if(n == 1 || n == 2) return 1;

        vector<int> dp(n+1);
        dp[0] = 0; dp[1] = 1; dp[2] = 1;   // 初始化dp表

        for(int i = 3; i <= n; i++){    
            dp[i] = dp[i-1] + dp[i-2] + dp[i-3];// 根据状态转移方程推导dp表
        }
        return dp[n];
    }

2. 三步问题

不同于泰波那契数列,本题就需要我们自己去推一推各个步骤了。

首先我们用dpn来表示上到n阶台阶有的方式个数(为了方便数据的映射,我们选择浪费掉dp0这个空间),dp1只有一种方法,所以dp1 = 1;dp2可以直接从0阶到2阶,也可以从1阶到2阶,所以dp2 = dp1 + 1 = 2;dp3可以直接从0到3阶,也可以先到1阶再一步跨到3阶,也可以先到2阶再一步跨到3阶,所以dp3 = dp1 + dp2 + 1 = 4。

由此我们可以推断出更广泛的情况,即到第n阶台阶可以有先到n-1或n-2或n-3的台阶后,再跨一步到达n阶台阶,所以状态转移方程为dpn = dpn-1 + dpn-2 + dpn-3

推导出核心的状态转移方程后遍历,最后输出结果即可。

示例代码:

cpp 复制代码
    int waysToStep(int n) {
        const int mod = 1e9 + 7; // 模数
        if(n == 1 || n == 2)return n;
        if(n == 3)return 4;
        
        vector<int> dp(n+1);
        dp[1] = 1; dp[2] = 2; dp[3] = 4;

        for(int i = 4; i <= n; i++){
            dp[i] = ((dp[i-1] + dp[i-2])%mod + dp[i-3])%mod;
        }
        return dp[n];

由于本题数据较大,需要进行取模运算

3. 解码方法

这道题比前两道都要复杂一点,我们先初步定义dpi表示解码到下标为i位置的字符串时解法的个数, 那么推导dpi的状态转移方程就要分为两种情况,第一种:si作为一位数字解码,如果满足 '1' <= si <= '9', 那么就可以编码,dpi += dpi-1 。第二种:si-1和si组合成两位数进行解码,如果两位数组合起来**>=10且<=26** ,那也可以编码,dpi += dpi-2

dp0取决于第一个字符是否为'0',而dp1就相对复杂,和后续的dpi状态转移方程的情况类似,但又因为数组范围的问题导致要单独处理。

所以这里在处理初始化时用到了一点小技巧,避免了dp1的单独处理,**即主动浪费第一个空间,**这样dp在用状态转移方程时就不会超出数组范围。但是相应的,s 和dp 的映射关系也要改变,dp 的所有数值都要后移一位,多出来的dp0的数值则要根据题目来设置,如本题dp0就等于1。

最终示例代码:

cpp 复制代码
    int numDecodings(string s) {
        int n = s.size();
        vector<int> dp(n+1, 0);
        dp[0] = 1;
        dp[1] = s[0] != '0';
        if(n == 1) return dp[1];

        for(int i = 2; i <= n; i++){
            // dp[i]单独解码
            if(s[i-1] != '0'){
                dp[i] += dp[i-1];
            }

            // dp[i]和dp[i-1]一起解码
            int val = (s[i-2] - '0')*10 + s[i-1]-'0';
            if(val >= 10 && val <= 26){
                dp[i] += dp[i-2];
            }
        }
    return dp[n];
    }

总结:

这里举了三道例题来讲解动态规划算法,但学会了这三道题也只是对动态规划算法有了初步认识,后续还需要练习更多的动态规划的算法题来进一步掌握。

以上便是本篇文章的所有内容了,如果觉得又帮助的话可以点赞收藏加关注支持一下!

相关推荐
MC皮蛋侠客4 小时前
Google Test 单元测试指南
c++·单元测试·google test
艾莉丝努力练剑5 小时前
【Linux:文件】Ext系列文件系统进阶
linux·运维·服务器·c++·文件系统·文件io·ext
kkeeper~5 小时前
0基础C语言积跬步之数据在内存中的存储
c语言·数据结构·算法
2401_868534785 小时前
论企业网络设计
数据结构
wabs6666 小时前
关于贪心算法的一些自我总结【力扣45.跳跃游戏II】【灵感来源:代码随想录】
算法·贪心算法·复盘
2401_876964136 小时前
【湖北专升本】2026湖北专升本真题PDF+备考资料汇总
数据结构·人工智能·经验分享·深度学习·算法·计算机视觉
basketball6167 小时前
C++ NULL 和 nullptr 区别 以及 nullptr 的核心实现
java·开发语言·c++
嗝o゚7 小时前
CANN GE 算子融合——融合算法与调度策略
算法·昇腾·cann·ge
小江的记录本7 小时前
【JVM虚拟机】垃圾回收GC:垃圾回收算法:标记-清除、标记-复制、标记-整理、分代收集(附《思维导图》+《面试高频考点清单》)
java·jvm·后端·python·算法·安全·面试
Fre丸子_8 小时前
自定义文件夹选取功能
c++