【算法】动态规划+题型举例+详细分析

【算法】动态规划

1.概念主要思想及场景用法

Dynamic Programming
DP定义:动态规划是分治思想的延伸,通俗一点来说就是大事化小,小事化无的艺术。在将大问题化解为小问题的分治过程中,保存对这些小问题已经处理好的结果,并供后面处理更大规模的问题时直接使用这些结果。
动态规划具备了以下三个特点:

  1. 把原来的问题分解成了几个相似的子问题
  2. 所有的子问题都只需要解决一次
  3. 储存子问题的解。
    动态规划的本质,是对问题状态的定义和状态转移方程的定义(状态以及状态之间的递推关系)

动态规划问题一般从以下四个角度考虑

  1. 状态定义
  2. 状态间的转移方程定义
  3. 状态的初始化
  4. 返回结果

状态定义的要求:定义的状态一定要形成递推关系。
一句话概括:三特点四要素两本质
适用场景:最大值/最小值, 可不可行, 是不是,方案个数

2.题目练习

动态规划主要是一种思想,通过刷题才能更有经验。

2.1第1题 Fibonacci

【本题链接】

题目描述
大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0,第1项是1)。
n<=39

斐波拉契数列是一道很简单的题目,我想大家第一时间都会想递归

class Solution{
public:
 int Fibonacci(int n){
        // 初始值
 if (n <= 0){
 return 0;
 }
 if (n == 1 || n == 2) {
 return 1;
 }
        // F(n)=F(n-1)+F(n-2)
 return Fibonacci(n - 2) + Fibonacci(n - 1);
 }
};

递归的方法时间复杂度为O(2^n),随着n的增大呈现指数增长,效率低下当输入比较大时,可能导致栈溢出
在递归过程中有大量的重复计算。我们来通过动态规划的方法来分析题目:
动态规划的四个要素:
1.状态:第i项的值为F(i)
2.状态初始化:F(0) = 0,F(1) = 1
3.转义方程:F(n)=F(n-1)+F(n-2)
4.返回值:F(n)

class Solution3{
public:
 int Fibonacci(int n){
        // 初始值
 if (n <= 0){
 return 0;
 }
 if (n == 1 || n == 2) {
 return 1;
 }
 int fn1 = 1;
 int fn2 = 1;
 int result = 0;
 for (int i = 3; i <= n; i++){
            // F(n)=F(n-1)+F(n-2)
 result = fn2 + fn1;
            // 更新值
 fn1 = fn2;
 fn2 = result;
 }
 return result;
 }
};

2.2第2题 字符串分割(Word Break)

[本题链接]

题目描述
给定一个字符串s和一组单词dict,判断s是否可以用空格分割成一个单词序列,使得单词序列中所有的单词都是dict中的单词(序列可以包含一个或多个单词)。
例如:
给定s=“leetcode”;
dict=[“leet”, “code”].
返回true,因为"leetcode"可以被分割成"leet code".

这个题如果使用暴力方法查分会很麻烦,我们来用动态规划的思想解决:
还是通过题目找到四要素:
这个题的状态和状态转义方程是比较难找的,这也是动态规划的难点。
分析问题:字符串s能否被分割 ----->从而抽象出状态
状态:F(i):字符串前i个字符是否可以分割
分析问题:
eg:LeetCode可以被分成的可能性有
F(0)和[8,8] 状态:true
F(1)和[2,8] 状态:
F(2)和[3,8] 状态:
F(3)和[4,8] 状态:
F(4)和[5,8] 状态:true
F(5)和[6,8] 状态:
。。。。。。
通过分析:我们可以得出它的状态方程
状态方程:F(i):j < i && F(j) && [j+1,i]是否能在词典中找到,若能状态方程为true.
状态初始化:F(0) :true
返回值:F(s.size())
代码实现:

class Solution {
public:
    bool wordBreak(string s, unordered_set<string> &dict) {
        if(s.empty())
            return false;
        if(dict.empty())
            return false;
        //开数组初始化它的大小和状态
        vector<bool> array(s.size()+ 1,false);
        //动态规划四要素:状态初始化
        array[0] = true;
        for(int i = 1;i <= s.size();i++)
        {
            //状态转化方程:
            for(int j = 0;j < i;j++)
            {
                // F(i): true{j <i && F(j) && substr[j+1,i]能在词典中找到} OR false
                // 第j+1个字符的索引为j
                if(array[j] && dict.find(s.substr(j, i - j))!= dict.end())
                {
                    //满足条件,可以被分割
                    array[i] = true;
                    break;
                }
            }
        }
        return array[s.size()];
    } 
};

第3题 三角矩阵(Triangle)

【本题链接】

题目描述
给出一个三角形,计算从三角形顶部到底部的最小路径和,每一步都可以移动到下面一行相邻的数字,
例如,给出的三角形如下:
[↵ [2],↵ [3,4],↵ [6,5,7],↵ [4,1,8,3]↵]
最小的从顶部到底部的路径和是2 + 3 + 5 + 1 = 11。
注意:
如果你能只用O(N)的额外的空间来完成这项工作的话,就可以得到附加分,其中N是三角形中的行总数。

在这里插入图片描述
分析问题:从顶部到底部的最小路径和
状态:涉及到位置使用二维数组
F(i,j):从(0,0)到(i,j)的最小路径和
状态转换方程:
分析:
情况一:
到达F(i,j)只有两条路,F(i-1,j-1)和F(i-1,j)选择一条最短的,再加上我们自己
即:
F(i)(j) = min(F(i-1,j-1),F(i-1,j)) + array[i][j]
eg:
在这里插入图片描述
情况2:
对于边上的点,从上边节点到达它的路径只有一条
即:
if(j == 0 ):F(i-1,0)+array[i][0];
if(j == i ):F(i-1,j -1)+array[i][j];

初始状态
F(0,0) = array[0][0]
**返回结果:**最后一行的最短路径
min(F(row - 1,j))

class Solution {
public:
    int minimumTotal(vector<vector<int> > &triangle) {
        int row = triangle.size();
        int col = triangle[row - 1].size();
        //遍历二维数组,算出它到达底部所有值的最短路径和
        for(int i = 1;i < row;i++)
        {
            for(int j = 0;j <= i;j++)
            {
                if(j == 0)
                    triangle[i][j] = triangle[i - 1][j] + triangle[i][j];
                else if(j == i)
                    triangle[i][j] = triangle[i - 1][j - 1] + triangle[i][j];
                else
                    triangle[i][j] = min(triangle[i - 1][j - 1],triangle[i-1][j]) + triangle[i][j];
            }
        }
        int minsum = triangle[row - 1][0];
        for(int j = 1;j < col;j++)
        {
            minsum = min(minsum,triangle[row-1][j]);
        }
        return minsum;
    }
};

总结:感觉现在笔试动态规划越来越多了,要多多练题才能更好的理解动态规划。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值