第十六天:动态规划

今天是释然发题解的第十六天,以后每一天都会和大家分享学习路上的心得,希望和大家一起进步,一起享受coding的乐趣。
本文约2700字,预计阅读10分钟
昨天我们学习了快速排序,忘记的小伙伴们可以看一下哦:

快速排序

今天我们来聊一聊动态规划,明天和大家分享动态规划的相关题目:

定义:

动态规划这个东西吧,定义有点复杂,我们先来通过例题入手:
例子: 一个含有n阶的楼梯,一次可以走1步或2步,从底走到顶一共有几种走法? 3<=n<=90
( 4660046610375530309)
这道题在我们学习递归的时候已经非常熟悉了,但是随着它数据的变大,它的复杂度成指数型增长

递归
f(1)=1;
f(2)=2;
f(n)=f(n-1)+f(n-2);

我们尝试一下在计算机里面输入楼梯数是90,发现它直接就崩溃了。。。。(2^90=1.2379400392854e+27)

那我们就会想着优化去降低它的复杂度
我们会发现中间会存在很多地重复调用,我们可以判断是否计算过来减少计算的次数,这其实就是递归调用最大的不便之处,虽然代码简单,但是随着次数的增加,计算的空间和时间都相当的恐怖

因此优化一下:

#include<iostream>
using namespace std;
int f[100];
bool bn[100];
int fun(int m)
{
    if(!bn[m])//检查是否已经计算过
    {
        f[m]=fun(m-1)+fun(m-2);//保存已经计算过的值
        bn[m]=true;
    }
    return f[m];
}
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<n;i++)
        bn[i]=false;
    f[1]=1;f[2]=2;
    bn[1]=bn[2]=true;
    cout<<fun(n)<<endl;
    return 0;
}

当我们尝试去用一个标记数组发现还是不行,得到的竟然是负值:
说明我们定义的数据范围不够,需要用long long

#include<iostream>
using namespace std;
long long f[100];
bool bn[100];
long long fun(int m)
{
    if(!bn[m])//检查是否已经计算过
    {
        f[m]=fun(m-1)+fun(m-2);//保存已经计算过的值
        bn[m]=true;
    }
    return f[m];
}
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
        bn[i]=false;
    f[1]=1;f[2]=2;
    bn[1]=bn[2]=true;
    cout<<fun(n)<<endl;
    return 0;
}

综上,动态规划是一种优化型的搜索或者说哦记忆性搜索,解决的是多阶段的问题,划分成小问题(类似于分治)如果在解决问题的时候不需要更新之前的状态,那么这种方法叫做贪心法。
如果我们不能仅有上一状态推导到下一状态,那么这个时候就需要用动态规划算法来进行决策,我们写出状态转移方程,通过自底向上的方式来解决问题

动态规划解题的基本步骤

1.明确dp数组的含义:

就是我们要有足够的信心相信dp数组它能够解决问题

2.找出递归关系式:

这应该算是最难的一步了吧,但是我们也可以通过分析情况来找:
(1)通过考虑每一次决策与否来写出对应的状态转移方程(递推关系式)
(2)考虑不同的情况,采用加法原理判断到达这一状态的可能性
(3)…应该还有我还没有总结到的…
我个人是不太喜欢最优子结构这种专用名词,我在一开始的学习的时候其实是没有怎么管这种词的,当我们学了一定境界的时候自然也会明白了的,初学者是不需要考虑这么多的,emmm,当然理解本质肯定会更好

3.就是初始化了

比如拿斐波那契数列来说,我们需要知道第一项和第二项的值来进行后面的递推,相当于递归里的边界条件

基本上绝大多数题目都是可以用这三个方法来解决的,动态规划一般解决的很多都是字符串有关的问题,

好了这个时候我们来举一个例子:

例题:不同路径(leetcode62)

传送门
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。

问总共有多少条不同的路径?
例如,这是一个3*7的网格
例如,上图是一个7 x 3 的网格。有多少可能的路径?

示例 1:

输入: m = 3, n = 2
输出: 3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。

  1. 向右 -> 向右 -> 向下
  2. 向右 -> 向下 -> 向右
  3. 向下 -> 向右 -> 向右
    示例 2:

输入: m = 7, n = 3
输出: 28
提示:
1 <= m, n <= 100
题目数据保证答案小于等于 2 * 10 ^ 9

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/unique-paths

解题思路一:数学的思想

因为机器到底右下角,向下几步,向右几步都是固定的,

比如,m=3, n=2,我们只要向下 1 步,向右 2 步就一定能到达终点
示例中,m=3,n=7,就是向下走2步,向右走6步

所以有 C m-1
C m+n−2
这个方法太强大了,只有数学好的人才能想明白,当然我们在比赛中数学好的人。那肯定是占相当大的优势的
具体就是算一下组合数就行了

解题思路二:动态规划

咱们来一步一步来:

1.明确dp数组的含义:

dp[ i ] [ j ]就是指走到(i,j)这个格子的步数,因为从左向右,从上往下,所以会有明确的状态转移方程

2.找出递归关系式:

那么我们细化到每一步,每一步只能由上面的格子和下面的格子得到,那么因此

 dp[i][j] = dp[i-1][j]+dp[i][j-1];//是不是很简单

3.初始化

因为每一个格子都必须要从上面的格子和下面的格子得到,因此需要对最先的行和列进行初始化:

for(int i = 1; i <= m; i++)
    {
        dp[i][1] = 1;
    }
    for(int i = 1; i <= n; i++)
    {
      dp[1][i] = 1;
    }

最后输出dp[i][j]就行啦!是不是超级简单呢?
好了,今天的动态规划就到这里。
释然每天发布一点自己学习的知识,希望2年后我们也能在ACM的赛场上见面,一起去追寻自己的程序猿之路吧!

后期也会和大家一起分享学习心得和学习经验呢,明天我们不见不散哦!

下期预告:

动态规划之线性动态规划

如果大家有什么建议或者要求请后台留言,释然也想和大家一起进步呀!
联系方式:shirandexiaowo@foxmail.com

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Shirandexiaowo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值