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

目录

前言:

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

[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的泰波那契数的值
  • 第二步,状态转移方程,本题题目中已经给出,dp[n+3] = dp[n] + dp[n+1] + dp[n+2]
  • 第三步,初始化,由于状态转移方程需要至少三个值,所以前三个泰波那契数都需要进行初始化,分别初始化为0,1,1
  • 第四步,本题的计算顺序是从小推大
  • 第五步,最终返回dp[n]的值即可

示例代码:

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. 三步问题

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

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

由此我们可以推断出更广泛的情况,即到第n阶台阶可以有先到n-1或n-2或n-3的台阶后,再跨一步到达n阶台阶,所以状态转移方程为dp[n] = dp[n-1] + dp[n-2] + dp[n-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. 解码方法

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

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

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

总结:

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

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

相关推荐
Archie_IT2 小时前
「深入浅出」嵌入式八股文—P2 内存篇
c语言·开发语言·数据结构·数据库·c++·算法
MATLAB代码顾问2 小时前
MATLAB计算标准径流指数(Standard Runoff Index,SRI)
数据结构·算法·matlab
Wadli2 小时前
C++面经|小林coding|(1)
开发语言·c++
HY小海2 小时前
【C++】map和set的使用
开发语言·c++
进击的圆儿3 小时前
【学习笔记02】C++面向对象编程核心技术详解
c++·笔记·学习
qq_574656253 小时前
java代码随想录day50|图论理论基础
java·算法·leetcode·图论
ajassi20003 小时前
开源 C++ QT QML 开发(七)自定义控件--仪表盘
c++·qt·开源
想ai抽4 小时前
吃透大数据算法-霍夫曼编码(Huffman Coding)
大数据·数据结构·算法
奔跑吧邓邓子4 小时前
【C++实战(71)】解锁C++音视频开发:FFmpeg从入门到实战
c++·ffmpeg·实战·音视频