6.21 数学简单&中等 动态规划专题 总结

动态规划算法常用于解决的问题:

这部分内容【动态规划算法常用于解决的问题】是因为后面在做题的时候经常分析不出一道题是否符合DP算法,所以问了chatgpt,下面内容大家自行判断,我这里只是用于自己整理思路用的,其余部分是自己整理的

  • 重叠子问题:如果一个问题可以被分解为多个重叠的子问题,且这些子问题之间有重复计算的部分,那么动态规划是一个很好的选择。例如,在计算斐波那契数列时,fib(n) 的值依赖于 fib(n-1) 和 fib(n-2),而这两个值会在计算过程中多次被用到。
  • 最优子结构:如果一个问题的最优解包含了其子问题的最优解,这通常意味着动态规划可以应用。典型的例子是背包问题,其中最优解可以通过解决更小的子问题(如剩余容量的背包问题)获得。
  • 状态转移:问题的解可以通过已知状态(子问题的解)转移得到新的状态。例如,在字符串分割(word break)问题中,dp[i] 的值是基于 dp[j] (0<j<=i)的状态转移而来。
  • 多种选择的组合:如果问题涉及多个选择的组合,并且需要找到唯一的最佳或可行解,这也通常意味着动态规划可能适用。例如,路径选择问题需要在多个路径中找到最短或最优路径。

问题对应的内容:
分解问题:尝试将问题分解为更小的子问题,看看是否存在重叠的子问题。如果是,那么动态规划可能适用。
是否需要记忆化:如果在递归解决方案中,你发现你需要记忆化某些中间结果来避免重复计算,这也是动态规划的标志。
递归与子问题的关系:尝试用递归解决问题,并观察是否可以通过递归关系来定义问题。如果能通过子问题的解推导出整个问题的解,这表明动态规划可能是一种合适的方法。

解题思路 使用数组解决 vector vector<vector>

  1. 首先确定要求的东西是什么(题目中要求return的内容),含义,以及对应的数组形状;
  2. 找出关系式,也就是将大问题拆分成的小问题是怎样的
  3. 初始条件,把限制边界全部设定好,也就是这些问题的起点,初始,也是关系式可以有实际数值的起源

509 斐波那契数列

思路: 思路:这个好像是有什么特殊的算法的,好像是交换,但这里我先暴力拆解??首先确定循环次数,然后设置好n0 和 n1的交换,每次循环fib = n0+n1。前提是n !=0 && n!=1。【自写】
class Solution {
public:
    int count(int n){
        int n0 = 0, n1 =1, fib =0;
        n-=1;
        while(n--){
            fib = n0 + n1;
            n1 = fib;
            n0 = n1 - n0;
        }
        return fib;
    }
    int fib(int n) {      
        if(n == 0 )
            return 0;
        else if(n == 1)
            return 1;
        else
            return count(n);

    }
};

动态规划的描述图,更直观理解, p =q q=r r= p+q

破案了,是动态规划,但总感觉没触及灵魂,本来是想用上面动图的思路,但是感觉没办法提取出来通用方法,先用最上面的思路写
class Solution {
public:
    int fib(int n) {
        if (n <= 1) {
            return n;
        }
        // 找出问题所求,第n个fib数
        vector<int> f(n + 1); // 初始化大小为 n+1 的 vector
        // 初始条件
        f[0] = 0;
        f[1] = 1;
        // 关系式, 第n个 = 第n-1 + n-2个
        for (int i = 2; i <= n; i++) {
            f[i] = f[i - 1] + f[i - 2];
        }
        return f[n];
    }
};

70 爬楼梯

思路:我想着就分析最后一次可以是1 也可以是2,然后就爬,倒着减 1 or 2,当爬完了就ans++,但是对于测试用例44他就跑不通了,看了题解还是和上一题一样的情况???这样也可以???好像答案都没怎么改???
class Solution {
public:
    int ans = 0;
    int climbStairs(int n) {
        if(n>=1)
            climbStairs(n-1);
        if(n>=2)
            climbStairs(n-2);
        return n == 0? ans++: ans;
    }
};
通法代码
class Solution {
public:
    int climbStairs(int n) {
        if(n <= 2){
            return n;
        }
        //找出问题所求, 爬到n层楼的方法数
        vector<int> way(n+1);
        //初始条件, 爬到1层 1种方式 2层 2种方式 3层
        way[0] = 1 , way[1] = 2 ;
        //关系式, 爬到最后一层的方法数 = 爬到倒数第二层和倒数第一层的方法数之和
        for(int i = 2; i<n ; i++){
            way[i] = way[i-1] + way[i-2];
        }
        return way[n-1];
        
    }
};

62 不同路径

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?

class Solution {
public:
    int uniquePaths(int m, int n) {

        if(m == 1 || n== 1)
        return 1;
        //找出问题所求, 走到第m行第n列的路径数目,注意这里数组存储的元素是到mn的路径数目,不是步数啥的
        vector<vector<int>> way(m+1,vector<int>(n+1,0));
        //初始条件, 开始只能向右或者向下,最左边[i][0]的只能从上边过来,最上边[0][i]的只能从右边过来
        way[0][0] = 0, way[0][1] = 1, way[1][0] = 1;
        for(int i = 2;i<m;i++){
            way[i][0] = way[i-1][0];
        }
        for(int i = 2;i<n;i++){
            way[0][i] = way[0][i-1];
        }
        //关系式, [m][n]需要从上或者左过来 = [m-1][n] + [m][n-1]
        for(int i = 1; i<m ; i++){
            for(int j = 1;j<n;j++){
                way[i][j] = way[i-1][j] + way[i][j-1];
            }           
        }
        return way[m-1][n-1];
    }
};
c++学习【二维数组初始化】
vector<vector<int>> double_matrix(m,vector<int>(n,0));

63 不同路径 II 之 障碍物

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。
现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
网格中的障碍物和空位置分别用 1 和 0 来表示。

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        int m = obstacleGrid.size() , n = m>0?obstacleGrid[0].size():0;
        if(m == 1 || n== 1){
            for(int i = 0 ;i<m;i++){
                for(int j = 0 ; j <n ;j++){
                    if(obstacleGrid[i][j] == 1)
                        return 0;
                }
            }
            return 1;
        }
        if(obstacleGrid[0][0] == 1 || obstacleGrid[m-1][n-1] == 1)
            return 0;
        //找出问题所求, 走到第m行第n列的路径数目
        vector<vector<int>> way(m+1,vector<int>(n+1,0));
        //初始条件, 开始只能向右或者向下,最左边[i][0]的只能从上边过来,最上边[0][i]的只能从右边过来
        //多增加的障碍物,导致了障碍物周围的路径发生了变化,主要是它本身这个位置的路径为0
        //这里注意,只要在设定way数组元素的时候进行置零就好,它的影响表示为:这条路不能走了,对于周边的元素来说就是这条路是到不了自己的位置的,所以在计算路径数目的时候+0就可以了
        way[0][0] = 1;
        for(int i = 1;i<m;i++){
            if(obstacleGrid[i][0] == 1){
                way[i][0] = 0;
            }else{
                way[i][0] = way[i-1][0];
            }
            
        }
        for(int i = 1;i<n;i++){
            if(obstacleGrid[0][i] == 1){
                 way[0][i] = 0;
            }else{
                 way[0][i] = way[0][i-1];
            }
           
        }
        
        //关系式, [m][n]需要从上或者左过来 = [m-1][n] + [m][n-1]
        for(int i = 1; i<m ; i++){
            for(int j = 1;j<n;j++){
                if(obstacleGrid[i][j] == 1)
                    way[i][j] = 0;
                else
                    way[i][j] = way[i-1][j] + way[i][j-1];
            }           
        }
        return way[m-1][n-1];
    }
};

64 最小路径和

给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
         int m = grid.size() , n = grid[0].size();
         int mans = 0;
         if(m == 1 || n== 1){
            for(int i = 0 ;i<m;i++){
                for(int j = 0 ; j <n ;j++){
                   mans += grid[i][j];
                }
            }
            return mans;
        }
        //找出问题所求, 走到第m行第n列的最短路径
        vector<vector<int>> way(m+1,vector<int>(n+1,0));
        //初始条件, 开始只能向右或者向下,最左边[i][0]的只能从上边过来,最上边[0][i]的只能从右边过来
        //唯一需要注意的是,way不是路径数目,是最短路径,即最短距离,也就是只包含了一条路,不要惯性思维了
        way[0][0] = grid[0][0];
        for(int i = 1;i<m;i++){
            way[i][0] = way[i-1][0] + grid[i][0];
            
        }
        for(int i = 1;i<n;i++){
            way[0][i] = way[0][i-1] +  grid[0][i];           
        }
        
        //关系式, [m][n]需要从上或者左过来 那么就取从上面过来或者从左边过来的较小的值 [m-1][n] > [m][n-1]?[m][n-1]:[m-1][n]
        for(int i = 1; i<m ; i++){
            for(int j = 1;j<n;j++){
                if(way[i-1][j] < way[i][j-1]){
                    way[i][j] = way[i-1][j] + grid[i][j];
                }else{
                    way[i][j] = way[i][j-1] + grid[i][j];
                }
            }           
        }
        return way[m-1][n-1];
    }
};

8.15 139 Word Break

在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        //动态规划
        //要做的事情是确定s中前i位是否可以被分割 dp[i]
        vector<bool> dp(s.size()+1,false);
        unordered_set<string> wordSet(wordDict.begin() , wordDict.end());
        //初始条件dp[0] = true;
        dp[0] = true;
        //遍历string s找合适的切割组合
        for(int i = 1 ; i <= s.size() ; i++){
            //判定是否可以被分割
            for(int j = 0 ; j < i ; j++){
                //判定条件,关系式:wordSet.find(s.substr(j , i-j)) != wordSet.end()
                if(dp[j] && wordSet.find(s.substr(j , i-j)) != wordSet.end()){
                    //可以被分割
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[s.size()];
    }
};

8.15 review 467 Unique Substrings in Wraparound String

在这里插入图片描述在这里插入图片描述

class Solution {
public:
    int findSubstringInWraproundString(string s) {
        //不同的非空的子串数目 不同+非空怎么判定-->记录数组dp 仅记录以26字母为结尾的
        //要找的是 且是不同的 存在于base中的子串 所以判定dp[i]为以i = ch - 'a'为索引,以ch为结尾的最长子串
        vector<int> dp(26,0);
        int maxLen = 0;
        //base的判定前者-后者为1 或者前者为z后者为a,记录长度
        for(int i = 0 ; i < s.size() ; i++){
            char ch = s[i];
            if(i>0 && (s[i] - s[i-1] == 1 || (s[i-1] == 'z' && s[i] == 'a'))){
                maxLen++;
            }else{
               //初始条件
               maxLen = 1;
            }
            dp[ch-'a'] = std::max(maxLen,dp[ch-'a']);
        }
        int ans = 0;
        for(int len : dp){
            ans += len;
        }
        return ans;
    }
};
参考文献

1.告别动态规划,连刷40道动规算法题,我总结了动规的套路

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值