LeetCode --- 动态规划(一)

LeetCode前200道题中的动态规划 

目录

LeetCode前200道题中的动态规划 

10. 正则表达式匹配

44. 通配符匹配

62. 不同路径

64. 最小路径和

72. 编辑距离

91. 解码方法

96. 不同的二叉搜索树

95. 不同的二叉搜索树 II

97. 交错字符串

115. 不同的子序列

174. 地下城游戏


10. 正则表达式匹配

 

比较难的一道动态规划,给出两种方法。

当前位置不是*比较容易,是*的时候要考虑可以不用前面的符号和用多次的问题

class Solution {
public:
    bool match(char* str, char* pattern)
    {
        int m = strlen(str),n = strlen(pattern);
 
        bool dp[m+11][n+11];
        memset(dp,0,sizeof dp);
        dp[0][0] = true;
 
        for(int i = 0;i<=m;i++)
            for(int j = 1;j<=n;j++){
                if(pattern[j-1] == '*')
                    dp[i][j] = dp[i][j-2] || (i>0 && str[i-1] == pattern[j-2] || pattern[j-2] == '.') && dp[i-1][j];//不用* 或者用* &&dp[i-1][j]是因为因为*可以匹配多个
                else dp[i][j] = i>0 && (str[i-1] == pattern[j-1] || pattern[j-1] == '.') && dp[i-1][j-1];//
 
            }
 
        return dp[m][n];
    }
};


class Solution {
public:
    bool match(char* str, char* pattern)
    {
        if(*str == 0 && *pattern == 0)return true;
        if(*str != 0 && *pattern ==0)return false;//str还有,pattern 没了
         
        if(*(pattern+1) != '*'){
            if(*str == *pattern || *str != 0 && *pattern == '.'){//匹配
                return match(str+1,pattern+1);//向前走
            }
            return false;//不匹配,返回false
        }
         
        else{
            if(*str == *pattern || *str!=0 && *pattern == '.')//当前能匹配
                return match(str,pattern+2) || match(str+1,pattern);//直接跳过下个* 或者 看带着*,str继续走(可匹配多个)
            return match(str,pattern+2);
        }
         
    }
};

 

 

44. 通配符匹配

 

class Solution {
public:
    bool isMatch(string s, string p) {
        int n = s.size(), m = p.size();
        vector< vector<bool> > dp(n+1, vector<bool>(m+1,false) );
        dp[0][0] = true;
        
        for(int i = 1;i<=m;++i) if(p[i-1] == '*')
            dp[0][i] = dp[0][i-1];
        
        for(int i = 1;i<=n;++i)
            for(int j = 1;j<=m;++j){
                if(p[j-1] == '*')
                    dp[i][j] = dp[i-1][j] || dp[i][j-1];//匹配字符串或者空串
                else 
                    dp[i][j] = (s[i-1] == p[j-1] || p[j-1] == '?') && dp[i-1][j-1];
                
            }
        return dp[n][m];
        
    }
};

62. 不同路径

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

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

问总共有多少条不同的路径?

 

class Solution {
public:
    
    int uniquePaths(int m, int n) {
        
        vector< vector<int> > dp(n, vector<int>(m,0) );
        
        for(int i = 0;i<n;++i)
            for(int j = 0;j<m;++j){
                if(i == 0 || j == 0)dp[i][j] = 1;
                else dp[i][j] = dp[i-1][j] + dp[i][j-1];
                
            }
        return dp[n-1][m-1];
    }
};

 

 

64. 最小路径和

 

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        
        int n = grid.size();
        if(!n)return 0;
        int m = grid[0].size();
        vector< vector<int> > dp(n, vector(m,0) );
        dp[0][0] = grid[0][0];
        for(int i = 0;i<n;++i)
            for(int j = 0;j<m;++j){
                if(!i && !j)continue;
                if(!i && j)dp[i][j] = dp[i][j-1] + grid[i][j];
                else if(i && !j)dp[i][j] = dp[i-1][j] + grid[i][j];
                else if(i && j)dp[i][j] = grid[i][j] + min(dp[i-1][j],dp[i][j-1]);
            }    
        return dp[n-1][m-1];
        
        
    }
};

 

 

72. 编辑距离

好吧,听说这届省赛出的这道题。一道经典的动态规划:

假设序列S和T的当前长度分别为m和n, 两者的编辑距离表示为dp[m][n]. 则对序列进行操作时存在以下几种情况:

 1、当S和T的末尾字符相等时, 对末尾字符不需要进行上述定义操作中(亦即"编辑")的任何一个, 也就是不需要增加计数. 则满足条件: dp[m][n] = dp[m - 1][n - 1].
 2、 当S和T的末尾字符不相等时, 则需要对两者之一的末尾进行编辑, 相应的计数会增加1.
 3、 对S或T的末尾进行修改, 以使之与T或S相等, 则此时dp[m][n] = dp[m - 1][n - 1] + 1;
 4、 删除S末尾的元素, 使S与T相等, 则此时dp[m][n] = dp[m - 1][n] + 1;
 5、 删除T末尾的元素, 使T与S相等, 则此时dp[m][n] = dp[m][n - 1] + 1;
 6、 在S的末尾添加T的尾元素, 使S和T相等, 则此时S的长度变为m+1, 但是此时S和T的末尾元素已经相等, 只需要比较S的前m个元素与T的前n-1个元素, 所以满足dp[m][n] = dp[m][n - 1] + 1;
7、 在T的末尾添加S的尾元素, 使T和S相等, 此时的情况跟b4相同, 满足dp[m][n] = dp[m - 1][n] + 1;
8、 比较特殊的情况是, 当S为空时, dp[0][n] = n; 而当T为空时, dp[m][0] = m; 这个很好理解, 例如对于序列""和"abc", 则两者的最少操作为3, 即序列""进行3次插入操作, 或者序列"abc"进行3次删除操作.
 

class Solution {
public:
    int minDistance(string word1, string word2) {
        int n = word1.size();
        int m = word2.size();
        int dp[1111][1111];
        
        for(int i = 0;i<=n;++i)dp[i][0] = i;
        for(int i = 0;i<=m;++i)dp[0][i] = i;
        
        for(int i = 1;i<=n;++i)
            for(int j = 1;j<=m;++j){
                if(word1[i-1] == word2[j-1]) dp[i][j] = dp[i-1][j-1];
                else dp[i][j] = 1+min(dp[i-1][j],min(dp[i][j-1],dp[i-1][j-1]));
            }
        return dp[n][m];    
        
        
    }
};

 

 

91. 解码方法

一条包含字母 A-Z 的消息通过以下方式进行了编码:

'A' -> 1
'B' -> 2
...
'Z' -> 26

给定一个只包含数字的非空字符串,请计算解码方法的总数。

示例 1:

输入: "12"
输出: 2
解释: 它可以解码为 "AB"(1 2)或者 "L"(12)。

class Solution {
public:
    int numDecodings(string s) {
    //dp[n] = dp[n-1] + dp[n-2](n-1 n能组成)
        int n = s.size();
        int dp[111111];
        for(int i = 0;i<=n+2;i++)dp[i] = 0;
        dp[0] = 1;dp[1] = 1;
        if(s[0] == '0')dp[1] = 0;
        for(int i = 2;i <= n;i++)
        {
            dp[i] = s[i-1] == '0'? 0:dp[i-1];//当前是0 重新开始,不是的话先等于上面的
            if(  (s[i-2] == '1') || (s[i-2] == '2' && s[i-1] <= '6'  )   )//加上两位的
                dp[i] += dp[i-2];
        }
        
        return dp[n];
    
    }
};

 

96. 不同的二叉搜索树

给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种?

 

dp[i] : i个节点可以组成的二叉搜索树的个数。

可以得递推式:dp[i] = dp[0] * dp[i-1] + dp[1] * dp[i-2] * .......

初始化:dp[0] = dp[1] = 1;

class Solution {
public:
    int numTrees(int n) {
        //a[i] = a[0]*a[i-1] + a[1] * a[i-2] + ...
        
        vector<int> dp(n+1,0);
        dp[0] = dp[1] = 1;
        
        for(int i = 2;i<=n;++i)
            for(int j = 0;j<i;++j)
                dp[i] += dp[j] * dp[i-1-j];
        
        return dp[n];
        
    }
};

 

 

95. 不同的二叉搜索树 II

直接搜索,不过还是要有上个题的思想的:


class Solution {
public:
    vector<TreeNode*> generateTrees(int n) {
        if(n)return solve(1,n);
        else return std::vector<TreeNode*>{};
    }


    vector<TreeNode*> solve(int left,int right){
    	std::vector<TreeNode*> v;
    	if(left > right){
    		v.push_back(nullptr);
    		return v;
    	}

    	for(int i = left;i<=right;i++){
    		std::vector<TreeNode *> left_node = solve(left,i-1);
    		std::vector<TreeNode *> right_node = solve(i+1,right);
    		for(auto x:left_node)
    			for(auto y:right_node){
    				TreeNode *t = new TreeNode(i);
    				t->left = x;
    				t->right = y;
    				v.push_back(t);
    			}
    	}

    	return v;
    }
};

 

 

 

97. 交错字符串

 

字符串上简单的动规即可:

class Solution {
public:
    bool isInterleave(string s1, string s2, string s3) {         
        int len1 = s1.size(),len2 = s2.size(),len3 = s3.size();
        
        
        if(len1+len2 != len3)return false;
        if(!len3)return true;
        
        vector< vector<int> > dp(len1+1,vector<int>(len2+2,0) );
        dp[0][0] = 1;
        for(int i = 1;i <= len1 && s1[i-1] == s3[i-1];++i){
            dp[i][0] = 1;
        }
        
        for(int i = 1;i <= len2 && s2[i-1] == s3[i-1];++i)
            dp[0][i]  = 1;
        
        
        for(int i = 1;i<=len1;++i)
            for(int j = 1;j<=len2;++j){
                int k = i+j-1;
                if(s1[i-1] == s3[k] && dp[i-1][j])
                    dp[i][j] = 1;
                if(s2[j-1] == s3[k] && dp[i][j-1])
                    dp[i][j] = 1;
                
            }
        
        return dp[len1][len2];
        
    }
};

 

 

 

115. 不同的子序列

 

 

字符串上简单动规:

dp[i][j] = dp[i-1][j];(不用s中当前的字符)

如果是s[i] == t[i]的话,d[i][j] += dp[i-1][j-1];

class Solution {
public:
    int numDistinct(string s, string t) {
        int n = s.size(), m = t.size();
        
        vector< vector<long long> > dp(n+2, vector<long long>(m+2,0)  );
        
        for(int i = 0;i<=n;++i)dp[i][0] = 1;
        for(int i = 1; i <= n;++i)
            for(int j = 1;j<=m;++j){
                
                if(i<j)continue;
                if(s[i-1] == t[j-1])dp[i][j] = dp[i-1][j-1] + dp[i-1][j];
                else dp[i][j] = dp[i-1][j];
                
            }
        return dp[n][m];
        
        
    }
};

 

 

 

174. 地下城游戏

一些恶魔抓住了公主(P)并将她关在了地下城的右下角。地下城是由 M x N 个房间组成的二维网格。我们英勇的骑士(K)最初被安置在左上角的房间里,他必须穿过地下城并通过对抗恶魔来拯救公主。

骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻降至 0 或以下,他会立即死亡。

有些房间由恶魔守卫,因此骑士在进入这些房间时会失去健康点数(若房间里的值为负整数,则表示骑士将损失健康点数);其他房间要么是空的(房间里的值为 0),要么包含增加骑士健康点数的魔法球(若房间里的值为正整数,则表示骑士将增加健康点数)。

为了尽快到达公主,骑士决定每次只向右或向下移动一步。

 

编写一个函数来计算确保骑士能够拯救到公主所需的最低初始健康点数。

例如,考虑到如下布局的地下城,如果骑士遵循最佳路径 右 -> 右 -> 下 -> 下,则骑士的初始健康点数至少为 7。

 

题解:可以采用二分(写一个判断可达函数搜索),但是本题动态规划似乎更方便:

dp[i][j] : 骑士在(i,j)的时候最少需要的健康点数。

dp[i][j] < 0代表当前位置走到终点还剩健康点数,所以可以置为0,继续往前递推。

class Solution {
public:
    int calculateMinimumHP(vector<vector<int>>& mp) {
        int n = mp.size(),m = mp[0].size();
        vector< vector<int> > dp(n+1, vector<int>(m+1,0) );
        dp[n-1][m-1] = mp[n-1][m-1] >=0 ? 0: -mp[n-1][m-1];
        for(int i = n-1;i>=0;--i)
            for(int j = m-1;j>=0;--j){
                
                int _min = 0x3f3f3f3f;
                if(i < n-1)
                    _min = min(_min,dp[i+1][j] - mp[i][j]);
                if(j < m-1)
                    _min = min(_min,dp[i][j+1] - mp[i][j]);
                
                if(i == n-1 && j == m-1);
                else dp[i][j] = max(0,_min);
            }
        
        return dp[0][0]+1;
    }
};

 

 

 

 

### 回答1: 好的,我来用中文回复这个链接:https://leetcode-cn.com/tag/dynamic-programming/ 这个链接是 LeetCode 上关于动态规划的题目集合。动态规划是一种常用的算法思想,可以用来解决很多实际问题,比如最长公共子序列、背包问题、最短路径等等。在 LeetCode 上,动态规划也是一个非常重要的题型,很多题目都需要用到动态规划的思想来解决。 这个链接里包含了很多关于动态规划的题目,按照难度从简单到困难排列。每个题目都有详细的题目描述、输入输出样例、题目解析和代码实现等内容,非常适合想要学习动态规划算法的人来练习和提高自己的能力。 总之,这个链接是一个非常好的学习动态规划算法的资源,建议大家多多利用。 ### 回答2: 动态规划是一种算法思想,通常用于优化具有重叠子问题和最优子结构性质的问题。由于其成熟的数学理论和强大的实用效果,动态规划在计算机科学、数学、经济学、管理学等领域均有重要应用。 在计算机科学领域,动态规划常用于解决最优化问题,如背包问题、图像处理、语音识别、自然语言处理等。同时,在计算机网络和分布式系统中,动态规划也广泛应用于各种优化算法中,如链路优化、路由算法、网络流量控制等。 对于算法领域的程序员而言,动态规划是一种必要的技能和知识点。在LeetCode这样的程序员平台上,题目分类和标签设置十分细致和方便,方便程序员查找并深入学习不同类型的算法。 LeetCode动态规划标签下的题目涵盖了各种难度级别和场景的问题。从简单的斐波那契数列、迷宫问题到可以用于实际应用的背包问题、最长公共子序列等,难度不断递进且话题丰富,有助于开发人员掌握动态规划的实际应用技能和抽象思维模式。 因此,深入LeetCode动态规划分类下的题目学习和练习,对于程序员的职业发展和技能提升有着重要的意义。 ### 回答3: 动态规划是一种常见的算法思想,它通过将问题拆分成子问题的方式进行求解。在LeetCode中,动态规划标签涵盖了众多经典和优美的算法问题,例如斐波那契数列、矩阵链乘法、背包问题等。 动态规划的核心思想是“记忆化搜索”,即将中间状态保存下来,避免重复计算。通常情况下,我们会使用一张二维表来记录状态转移过程中的中间值,例如动态规划求解斐波那契数列问题时,就可以定义一个二维数组f[i][j],代表第i项斐波那契数列中,第j个元素的值。 在LeetCode中,动态规划标签下有众多难度不同的问题。例如,经典的“爬楼梯”问题,要求我们计算到n级楼梯的方案数。这个问题的解法非常简单,只需要维护一个长度为n的数组,记录到达每一级楼梯的方案数即可。类似的问题还有“零钱兑换”、“乘积最大子数组”、“通配符匹配”等,它们都采用了类似的动态规划思想,通过拆分问题、保存中间状态来求解问题。 需要注意的是,动态规划算法并不是万能的,它虽然可以处理众多经典问题,但在某些场景下并不适用。例如,某些问题的状态转移过程比较复杂,或者状态转移方程中存在多个参数,这些情况下使用动态规划算法可能会变得比较麻烦。此外,动态规划算法也存在一些常见误区,例如错用贪心思想、未考虑边界情况等。 总之,掌握动态规划算法对于LeetCode的学习和解题都非常重要。除了刷题以外,我们还可以通过阅读经典的动态规划书籍,例如《算法竞赛进阶指南》、《算法与数据结构基础》等,来深入理解这种算法思想。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值