动态规划—基础学习篇
**动态规划在求解问题中往往是解题利器**,刷题leetcode近两个月,还没有系统学习动态规划、贪心算法、回溯等,因此接下来一段时间,小白会定期发布自己系统学习的算法,帮助自己和大家理解,话不多说,直接进入正题。
其实查阅资料可知,动态规划无非就是两大步骤:①拆分问题;②确定状态及状态转移方程
对于以上两大步骤,小白通过“BS有前途”的博客中讲的通俗易懂,里面的例子也都很经典,不了解的小伙伴可以去看一下:[经典算法:动态规划](https://blog.youkuaiyun.com/ailaojie/article/details/83014821)
小白在本博客中就拿两道经典的leetcode题目来分享一些心得:
经典例题①
leetcode剑指Offer 10-1斐波那契数列
题目如下:
写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
还未学习动态规划前,你是否第一反应就是:哇,好解决,我通过递归直接掉用返回上一层的结果,然后归于下一层使用,或者直接使用双重for循环来算出F(n-1)和F(n-2),而这样做,会让时间复杂度难以估量的高(随着问题规模的增大)
那么,有什么好的解决办法呢?
答案无非是,动态规划的思想,我们通过保存当前问题的解,并将这个问题的解用于下一问题解的前提,来实现求得最后问题的解,拿F(8)举例,因为要求F(8),我们从当前已知的F(0)和F(1)求起,计算出F(2),这个时候,就能通过F(1)和F(2)来求解F(3),依次计算便可,代码如下:
class Solution {
//动态规划初尝试
public:
int fib(int n) {
//0、1需要单独考虑
if(n==0) return 0;
if(n==1) return 1;
vector<int> dp(n+1,0);
dp[0]=0;
dp[1]=1;
for(int i=2;i<n+1;i++)
dp[i]=(dp[i-1]+dp[i-2])%1000000007;
return dp[n];
}
};
时间复杂度为O(n)
斐波那契数列是一题经典的动态规划题目,从此为出发点能对动态规划有初步的了解
经典例题②(不同路径)
题目如下:
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?

状态初始化:每个第一行方格和第一列都只能从左或者只能从上方方格处到达,所以初始化第一行和第一列的方法数为1
因为每个到达每个方格的方法分别是从上方到达和从左方到达,因此可以列出动态转移方程:path[i][j]=p[i][j-1]+p[i-1][j]
代码如下:
class Solution {
public:
int uniquePaths(int m, int n) {
vector<vector<int>> paths(m,vector<int>(n)); //注意需要这样创建
//确定初始状态,边界情况皆为初始态
for(int j=0;j<n;j++)
{
paths[0][j]=1;
}
for(int i=0;i<m;i++)
{
paths[i][0]=1;
}
//每一个由左边方格和右边方格共同决定
for(int i=1;i<m;i++)
{
for(int j=1;j<n;j++)
{
//状态转移方程
paths[i][j]=paths[i-1][j]+paths[i][j-1];
}
}
return paths[m-1][n-1];
}
};
时间复杂度O(n²)
值得注意的是:二维数组vector<vector >path的初始化方法需要:
vector<vector> paths(lines,vector(rows));
意为创建行长为line,列宽为rows的二维数组,将这两个向量再用vector包裹起来
调试运行时不断报超时错误,是因为没有正确初始化导致循环无法停止,需要特别注意
今天是系统动态规划学习的第一天,是较为基础的理解和经典例题的反思,Fighting~