算法学习——动态规划的学习

2023-11-19

作者:小 琛
欢迎转载,请标明出处

引言:动态规划是算法中较难的内容,常常被用来区分学习者的档次,笔者也是在面试中发现了这个问题,故而回头认真学习并总结递归算法,希望能够帮助到阅读的你

动态规划的思想

“世界上的所有问题,都可以化繁为简,化简为空”。这种思想本身就是一门艺术,就像写代码的我们,同样在做一门艺术。

1. 动归从某种意义讲其实就是递归的优化
以前我们接触过一种思想:递归。一个大问题可以拆分为若干个小问题,而小问题最后变为了一个确定的、简单的问题。动态规划的思路本身就和递归类似:当遇到一个问题时,这个问题的解决是有规律可循的,可将该问题拆分为多个小问题来解决,例如斐波那契数列中,F(n)的求解等于F(n-1)+F(n-2)。

而当我们确定了这些规律后,动态规划所做的操作,就是将这类规律总结,形成一个动态转移方程,例如上面的斐波那契数列的F(n)=F(n-1)+F(n-2)本质就是一个动态转移方程。
2. 动归对于递归最大的优化就是用记录法代替栈帧的开销
C++学习者肯定明白,递归的过程就是函数栈帧的开辟,当递归次数增大而导致程序崩溃时,就是因为过多的栈帧导致的栈溢出。而动态规划常用的办法,定义数组dp(该数组需要根据需求设定,可能是一维,也可能二维),dp用来记录每一个状态,从而实现求解下一个状态的时候直接从之前的状态获得即可。
在这里插入图片描述

动态规划的具体实现步骤

  1. 确定问题。F(n)是一个什么样的求解
  2. 如果题目不好分析,可试写几个例子,帮助分析
  3. 将原问题拆分
  4. 确定动态转移方程
  5. 确定初始状态
  6. 使用记录法(数组dp)来记录每一个状态

入门级别的两个例子

斐波那契数列

写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.

题目分析:斐波那契数列常用来学习循环或递归,此处我们用动态规划思路解决。

  1. 问题,求F(n)
  2. 写几个简单例子帮助分析
    F(0)=0,F(1)=1,F(2)=2,F(3)=3,F(4)=5
  3. 拆分问题。F(n)的值为F(n-1)与F(n-2)的和
  4. 写转移方程:F(n)=F(n-1)+F(n-2)
  5. 确定初始状态,F(0)=0,F(1)=1
  6. 记录法,表示动归的每一步
    在这里插入图片描述
class Solution {
public:
    int fib(int n) {
        if (n==0)
            return 0;
        if (n==1)
            return 1;
        vector<int> dp(n+1,0);
        dp[1]=1;
        for (int i=2;i<n+1;i++)
        {
            dp[i]=(dp[i-1]+dp[i-2])%1000000007;
        }
        return dp[n];
    }
};
变态跳台阶问题

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶,也可以跳n级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

解题思路:这道题其实是一道经典的递归问题,而此处我们用动态规划来解答。仍然按照常规步骤:

  1. 问题:求F(n)级台阶的跳法。
  2. 写几个简单的例子帮助分析。
    n=1,F(n)=1;
    n=2,F(n)=2;
    n=3,F(n)=4;
    n=4,F(n)=8;
  3. 拆分成子问题:F(n)级台阶的跳法=F(n-1)+F(n-2)+F(n-3)+…F(1)+1
  4. 写转移方程。在动归中,转移方程需要满足的要求是:达到可确定公式,因此我们需要将上面的公式进一步化简,而F(n-1)=F(n-2)+F(n-3)+F(1)+1,因此F(n)=2F(n-1)
  5. 确定初时状态:dp[0]=1,dp[1]=1
  6. 采用记录法,来表示每一步动归
    在这里插入图片描述
#include <iostream>
#include <vector>
class Solution {
public:
    int numWays(int n) 
    {
     	vector<int> dp(n+1,0);
     	dp[0]=1;
     	for (int i=1;i<=n;i++)
     	{
     		dp[i]=2*dp[i-1];
     	}
     	return dp[n];
    }
};

动归的难点,当分析复杂化

经典题目1、string break

前面谈到,动态规划的核心,在于问题的确定、转移方程的列写、初始值的确定、记录数组的定义。当分析的问题难度提升,则前三步有难度时,动态规划题目级别就上升了,此时则需要认真分析,核心点还是在于抓住动态规划的解决步骤,将困难的问题简单化

题目:

给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
说明:
拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。
示例 1:
输入: s = “leetcode”, wordDict = [“leet”, “code”]
输出: true
解释: 返回 true 因为 “leetcode” 可以被拆分成 “leet code”。

题目解答

  1. 确定问题。判断s中的若干部分是否可以分割成wordDict中的单词
  2. 举例帮助分析:le不可以;leet可以;leetc中leet可以但c不可以,因此不可以…
  3. 拆分问题:判断一个字符串是否可以分割,即针对每个字符处,从上一个满足要求的字符开始,到该字符处的这部分字符,是否满足分割要求,例如判断e处,则需要判断le是否满足;t处,需要判断leet;o处,可以是leetco亦可以从c开始co
  4. 转移方程:判断当前处是否满足分割,要看上一个满足分割要求的字符处开始到该处是否满足词典,如果满足则该字符处满足,否则该处不满足
    即:dp[i]=dp[j]+check(s[j]…s[i-1]) 其中i为当前下标,j为上一个满足的下标
  5. 确定初始状态:若字符为第0个,则一定满足
  6. 用记录法确定每一步动归
    在这里插入图片描述
bool wordBreak(string s, vector<string>& wordDict) {
        unordered_set<string> myset;
        for (auto str: wordDict)
        {
            myset.insert(str);
        }
        vector<bool> dp(s.size()+1);
        dp[0]=true;
        for (int i=1;i<dp.size()+1;i++)
        {
            for(int j=0;j<i;j++)
            {
                if (dp[j] && myset.find(s.substr(j,i-j))!=myset.end())
                {
                    dp[i]=true;
                    break;
                }
            }
        }
        return dp[s.size()];
    }

总结:该题目的难点在于最初的分析,很多时候无法和动态规划连接在一起,因此在遇到这类题目的时候,可以多举几个例子,查看分析当前的判断和之前的判断有何种联系,得到这种递推式的关系后,再列写状态转移方程。同时需要将之前学习过的知识联系起来,方可解答。

经典题目2、三角形最小路径和

j

题目分析:

该题是一道经典的动归题目。分析采用常规流程即可

  1. 问题:求小路径和
  2. 举例帮助理解:2-3-5-1
  3. 拆分问题,某一个位置的路径和为上一层所在点所能走的两个点的最小值,但唯一最左边和最右边需要特殊处理
  4. 动态转移方程:F(x,y)=min(F(x-1,y-1),F(x,y-1))+triangle[x,y]
  5. 初始状态:对于[0,0]点,路径为该点本身的值
  6. 用数组记录方式统计每一个状态
    在这里插入图片描述
     int minimumTotal(vector<vector<int>>& triangle) {
         int n=triangle.size();
         vector<vector<int>> dp(n,vector<int>(n));
         dp[0][0]=triangle[0][0];
         int i,j;
         for ( i=1;i<n;i++)
         {
             dp[i][0]=dp[i-1][0]+triangle[i][0];
             for ( j=1;j<i;j++)
             {
                 dp[i][j]=min(dp[i-1][j-1],dp[i-1][j])+triangle[i][j];
             }
             dp[i][i]=dp[i-1][j-1]+triangle[i][j];
         }
         return *min_element(dp[n-1].begin(),dp[n-1].end());
    }
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

算法学习——动态规划的学习 的相关文章

随机推荐

  • 2019 icpc西安邀请赛 点分治

    https nanti jisuanke com t 39277 求 sum 异或和为0的路径 被其他路径包含的次数 如果只是求异或和为0的路径数量 其实是裸点分治 但是加上要求之后 就会复杂一些 进行分类讨论 再特殊处理根节点就行 由于信
  • Unraid使用记录:使用Docker与虚拟机

    文章目录 前言 使用Docker 使用示例 相关说明 使用虚拟机 使用示例 相关说明 硬件直通 后记 前言 Unraid本身功能挺少的 很多功能都是要通过插件 Docker和虚拟机来实现的 Docker可以简单的实现各种丰富的功能 而虚拟机
  • 群晖DS Video(Station)自动同步视频简介和海报(最新官方解决方案)

    目录 一 前言 二 前提 三 实现 1 注册The Movie Database账号 2 创建API 3 修改群辉Hosts A 在群辉中开启SSH的访问 B 然后通过ssh命令登录到群辉后台 C 通过sudo i指令切换到root用户指令
  • python的循环控制结构_Python的控制结构之For、While、If循环问题

    传统Python语言的主要控制结构是for循环 然而 需要注意的是for循环在Pandas中不常用 因此Python中for循环的有效执行并不适用于Pandas模式 一些常见控制结构如下 for循环 while循环 if else语句 tr
  • 【建议收藏!】APP UI自动化测试,思路全总结在这里了。

    首先想要说明一下 APP自动化测试可能很多公司不用 但也是大部分自动化测试工程师 高级测试工程师岗位招聘信息上要求的 所以为了更好的待遇 我们还是需要花时间去掌握的 毕竟谁也不会跟钱过不去 接下来 一起总结一下APP UI自动化测试的思路吧
  • 再论人与人的三大关系:生存关系、性关系和经济关系

    黄仁宇在 关系 一文中认为 人类的各种关系之中 以生存的关系 性关系和经济关系最为重要 理想上的工作协作和团队精神 已经不存在 俺做过的几个规模在50人以下的 这说明两个问题 1 小公司的目的不是发展而是不死 然后赚钱 也就是这是一笔买卖而
  • exe4j打包exe_JDK11及以后版本在Win下的打包发布方法

    概述 我在准备使用高版本jdk后 遇到的最麻烦的问题就是打包发布了 主要原因还是jdk的模块化带来的 在经历了长时间折腾后 终于成功完成了这个 当然 只是针对window下的 想要使用高版本jdk打包发布Windows应用 需要准备 exe
  • js中的对象 函数 原型

    关于 Function Object 和 proto prototype 1 每一个对象实例都有一个 proto 属性 这个属性就是指向 对象构造函数的原型 let b new Function console log b proto Fu
  • 【Matlab图片剪裁】

    标题Matlab剪裁图片 提取感兴趣部分 问题描述 当需要从一幅图片中提取一些感兴趣的内容时 比如一些细小的文字 图案等 如果从整个图片中直接提取 必然会大大增加计算量 导致处理时间很长 而且多数计算都是无效计算 进而非常消耗资源 解决办法
  • impala 错误

    问题一 impala state store unrecognized service 原因 当前节点未成功安装impala server impala state store impala catalog 解决方案 yum install
  • Qt生成log日志文件

    摘要 本文在Qt程序中实现了日志功能 读者可以在此基础上进一步创作和拓展 介绍 系统日志一般指存放系统重要运行信息的log txt文件 主要作用有两个 1 记录系统重要的运行信息 2 当系统突然崩溃时 可以根据日志来跟踪和定位程序错误 Qt
  • 常见CAD/CAM控件大全

    前言 CAD CAM 计算机辅助设计与制造 技术是随着计算机和数字化信息技术发展而形成的新技术 是20世纪最杰出的工程成就之一 也是数字化 信息化制造技术的基础 其发展和应用对制造业产生了巨大的影响和推动作用 经过几十年的发展和应用 不仅C
  • Python+Selenium爬虫之动态验证码的处理

    目录 1 拖动下方滑块完成拼图 单独图片 2 拖动下方滑块完成拼图 共同图片 可拖动验证码分为空缺区域为单独的图片和空缺区域与背景图片为一个共同图片 所以实现方式有2种 1 拖动下方滑块完成拼图 单独图片 拖动验证码 实现原理 查看空缺区域
  • [VScode]终端回应“pnpm : 无法将“pnpm”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。“解决思路

    问题概述 遇到问题 在VScode终端输入pnpm install有错误提示 pnpm 无法将 pnpm 项识别为 cmdlet 函数 脚本文件或可运行程序的名称 请检查名称的拼写 如 果包括路径 请确保路径正确 然后再试一次 所在位置 行
  • wps表格中的文字不能顺利水平居中的解决办法

    今天调整别人做的表格 想要把表格中文字水平居中 竟然指令操作后发现很多行的表格无动于衷 发现这样设置一下应该可以解决 WPS文字右边有个小的下拉箭头 选择 格式 段落进行设置 段前段后设置为0 行距设置为单倍行距或者固定行距 固定行距需要测
  • maven私服不能重复部署解决方法

    maven私服不能重复部署解决 1 报错 Return code is 400 ReasonPhrase Repository does not allow updating assets maven releases 2 原因 经排查发现
  • Elasticsearch 7.x生产配置

    虽然Elasticsearch需要很少的配置 但是有一些设置需要手动配置 并且必须在进入生产之前进行配置 1 官方文档 这些重要配置说明 请参考官方文档 https www elastic co guide en elasticsearch
  • c++得到窗口句柄

    include
  • Gitlab Java API 使用示例(亲测、有效)

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 简介 一 依赖 常量 Maven依赖 定义常量类 二 增删改查 1 新增私有仓库 2 删除指定仓库 3 修改项目简介和是否开源 三 后续更新 简介 在开发中 偶尔会
  • 算法学习——动态规划的学习

    作者 小 琛 欢迎转载 请标明出处 引言 动态规划是算法中较难的内容 常常被用来区分学习者的档次 笔者也是在面试中发现了这个问题 故而回头认真学习并总结递归算法 希望能够帮助到阅读的你 文章目录 动态规划的思想 动态规划的具体实现步骤 入门