算法:动态规划思路讲解(C/C++)

前两天偶然跟同事复盘,发现之前的功能业务里面的算法写的实在是不堪入目。整个算法结构超过50行...全是if,else🤣。主打的就是一个特殊问题定制讲解,不过这也暴漏了多个问题。

实际上,在很多开发任务中,涉及到功能模块时,因为每个人工作经验的丰富度不同,其写出来的模块所能承载的业务场景数量就不同。但毋庸置疑的是,动态规划的思想一定程度上,是可以让写的功能模块更稳定且优雅~

所以针对此情况,今天特地自己给自己写一篇关于动态规划的思想。

一、简单概括

动规,简而言之就是一种以大问题拆分小问题小问题以贪心思想拿到当前最优解,存储结果,进而与其他小问题进行比较,直至拿到整个小问题中最优解然后退出。

怎么理解这句话呢?比如,在同一个时刻,小李媳妇让他去送孩子上学,但上司打电话要求小李立刻修复一个影响项目上线BUG,然后此时地上的热水壶又被孩子chuang倒烫到了小李的脚0.o

好了,在整个场景下,让我们动态规划一下,如果你是小李,你要怎么处理该场景?

当然,这个例子不太恰当,毕竟每个人对于每个事情的看待情况不一样,不过这个例子仅仅是一个抽象层面的举例,不必太较真哈~

我们来分析,整体问题就是处理当前影响自己原本计划的事情,这里的问题①送孩子,问题②修复bug,问题③被热水烫伤。

如果拆分成问题结构为,①,①②,①②③。

结合当下社会主流价值观来看,如果仅有问题①,那么就去送孩子就可以了。如果问题是①②,那么肯定是小李修复bug,孩子交给他媳妇去送。如果问题是①②③,那啥也别说,先把脚用冷水冲一下,然后去修复bug,孩子交给他媳妇去送。

这个例子实则也就是在讲,当整个问题出现在面前时,尝试分析局部问题中出现该种情况要如何处理,最后在拿到基于当前问题的最优解即可。

二、具象化举例

这里我们以两道题来做例子吧。先来一道简单的题 牛客网<BM64 最小花费爬楼梯>

题目链接:最小花费爬楼梯_牛客题霸_牛客网

题目要求:给定一个整数数组 cost ,其中 cost[i]  是从楼梯第i \i 个台阶向上爬需要支付的费用,下标从0开始。一旦你支付此费用,即可选择向上爬一个或者两个台阶。你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。请你计算并返回达到楼梯顶部的最低花费。

简而言之,就是给你一个数组,你可以从下表为0,1开始爬,每次可以爬一个or两个,但不管爬一个还是爬两个,你需要付费当前下表内的元素价格。直到跃出数组元素个数才算结束。

分析一下这道题不难得出,首先,每一步的子问题在于,我这一个台阶,可以由上一个台阶+1得到,也可以由上上一个台阶+1得到。那我怎么确定我要选哪一个呢?

很简单,看一下如果是由上一个台阶+1得到,我们可以看看(上一个台阶)到达花费的费用加(上一个台阶)所属(数组下标的钱)对比(上上一个台阶)到达花费的费用加(上上一个台阶所属数组下下标的钱)。哪个小,我们就选哪个

具体实现:

    int minCostClimbingStairs(vector<int>& cost) {
        vector<int> dp(cost.size()+1,0);//存储每层台阶的结果
        for(int i = 2;i<=cost.size();i++) {
            dp[i] = min(cost[i-1]+dp[i-1],cost[i-2]+dp[i-2]);
        }//循环遍历获得从第2阶台阶开始的每一阶最优结果。
        return *(dp.end()-1);
    }

这里就有大佬说了,你举得这个例子这么简单,忽悠人是吧?

好好好,既然大佬这么说了,那我们稍微再加一点点难度。

如题:最长公共子序列(二)_牛客题霸_牛客网

题目要求:给定两个字符串str1和str2,输出两个字符串的最长公共子序列。如果最长公共子序列为空,则返回"-1"。目前给出的数据,仅仅会存在一个最长的公共子序列。数据范围:0 \le |str1|,|str2| \le 20000≤∣str1∣,∣str2∣≤2000。空间复杂度 O(n^2)O(n2) ,时间复杂度 O(n^2)O(n2)

简而言之,就是给定两个字符串,要求拿出里面相同的最长的子序列。(子序列不同于字串,他是可以跨字符获取的)

分析一下:在这里,假设有str1,str2。那么我们要做的,就是将两个字符串以二维数组形式进行划分,将相同的子序列个数所在坐标,存储至我们开辟的二维数组中

啥意思呢?如下图:

有的同学就要问了,啊为什么要空一行啊,列怎么不往左移动一格?

那是因为我们是要用dp[i][j]来存储相同字符的位置,所以要保证初始值dp[0][0] = 0,故开辟的空间大小也应该是Str1.size()+1和Str2.size()+1

具体代码:

class Solution {
public:
    string OneStr = "";
    string TwoStr = "";
    string GetStringResult(size_t i,size_t j,vector<vector<int>>& tempNum)
    {
        string res = "";
        if (i==0 || j==0) {
            return res;
        }
        if (tempNum[i][j]== 1) {
            res += GetStringResult(i-1, j-1,tempNum);//相同传入上一个字符位置,继续遍历
            res += TwoStr[j-1];//这里也可以写res += OneStr[i-1] 效果是一样的,因为我们每一级的字符位置,存的是上一个字符元素,所以叠加res时,要拿到字符元素内容
        }
        else if(tempNum[i][j]==2) { res += GetStringResult(i - 1, j, tempNum);//因为是来自于左边,所以我要继续跑到左边直到找到相同字符的标志位}
        else if(tempNum[i][j] == 3) {  res += GetStringResult(i,j - 1, tempNum);}
        return res;
    }

    string LCS(string s1, string s2) {
        OneStr = s1;
        TwoStr = s2;
        if (s1.length() == 0 || s2.length() ==0) {
            return "-1";
        }
        vector<vector<int>> dp(s1.size()+1,vector<int>(s2.size()+1,0));//存储相同长度大小
        vector<vector<int>> tempNum(s1.size()+1,vector<int>(s2.size()+1,0));//存储标志位
        for (int i = 1; i<=s1.size();i++) {
            for (int j = 1;j<=s2.size();j++) {
                if(s1[i-1] == s2[j-1]) {//表示字符相同,暂存位置
                    dp[i][j] = dp[i-1][j-1]+1;//基于上一个字符相同的长度+1
                    tempNum[i][j] = 1;//将本级位置设置flag
                }
                else {
                    if(dp[i-1][j] > dp[i][j-1]) { //不相同,来自于左边
                        dp[i][j] = dp[i-1][j];//延续左边的字符长度
                        tempNum[i][j] = 2;//将本级位置设置flag
                    } else {
                        dp[i][j] = dp[i][j-1];//延续上边的字符长度
                        tempNum[i][j] = 3;//将本级位置设置flag
                    }
                }
            }
        }
        string res = GetStringResult(s1.size(),s2.size(),tempNum);
        return res != "" ? res : "-1";
    }
};

综上,我们使用抽象和具象化讲解关于动态规划的思想。如有觉得不合理的地方,欢迎找博主讨论

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值