球球速刷LC之DP问题 三轮


DP的本质是巧妙定义状态,并找到状态的递推关系

经典

三角形最小路径和

状态:对于第i行Row[i], 假设以该行j元素Row[i][j]结尾的路径最小和为sum[i][j].
则sum[i][j]=Row[i][j]+min{sum[i-1][j-1],sum[i-1][j]};(注意j的边界)
最终求得最后一行里sum的最小值。

class Solution {
    
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        if(triangle.size() == 0) return 0;
               
        if(triangle.size() == 1){            
            return *min_element(triangle[0].begin(),triangle[0].end());            
        }
        //第0行的各个元素的最小路径和就是元素本身
        auto lastRow = triangle[0];
        for(int i = 1; i<triangle.size();++i){
            //取得当前行元素
            auto currRow = triangle[i];
            //对当前行元素遍历,计算以每个元素为路径结尾的最小和
            for(int j = 0;j<triangle[i].size();++j){                
                if(j==0){
                     currRow[j] = triangle[i][j]+lastRow[0];
                }else if(j==triangle[i].size()-1){
                     currRow[j] = triangle[i][j]+lastRow[j-1];
                }else{
                     currRow[j] = std::min(triangle[i][j]+lastRow[j],triangle[i][j]+lastRow[j-1]);
                }   
            }
            lastRow = currRow;
        }        
        return *min_element(lastRow.begin(),lastRow.end());        
    }
};

网格递推

到达路径数目333
class Solution {
public:
    int uniquePaths(int m, int n) {
        int dp[m][n];
        
        for(int i =0 ; i<m;++i){
            for(int j =0; j<n;++j){
                if(i == 0 || j==0){
                    dp[i][j]=1;
                }else{
                //依赖与上方与左边的方案数目
                    dp[i][j] = dp[i-1][j] + dp[i][j-1];
                }
            }
        }       
        return dp[m-1][n-1];
    }
};
到达路径数目2

相对于上一题,增加对障碍物判断

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
       long m=obstacleGrid.size();
       long n=obstacleGrid.at(0).size();        
       long dp[m][n];
        
        for(long i =0;i<m;++i){
            for(long j=0;j<n;++j){
               if(obstacleGrid.at(i).at(j) == 1){
                   dp[i][j]=0;
               }else{
                   if(i==0&&j==0) dp[i][j]=1;
                   else{
                       dp[i][j] =0;
                   }
                   if((i-1) >=0){
                       dp[i][j] += dp[i-1][j];
                   }
                   
                   if((j-1)>=0){
                       dp[i][j] += dp[i][j-1];
                   }
               }  
            }
        }        
        return dp[m-1][n-1];        
    }
};
最大正方形333

dp[i][j]代表以(i,j)为右下角顶点所能形成的最大正方形的边长

class Solution {
public:
    int maximalSquare(vector<vector<char>>& matrix) {        
        int m=matrix.size();
        if(m==0) return 0;        
        int n=matrix[0].size();
        int dp[m][n];
        
        int max_square=0;
        for(int i = 0 ; i<m;++i){
            for(int j=0;j<n;++j){
                if(i==0 || j==0) dp[i][j] = (matrix[i][j]=='1'?1:0);
                else {
                   if(matrix[i][j]=='1') dp[i][j] = std::min(dp[i-1][j],min(dp[i-1][j-1],dp[i][j-1]))+1;
                   else{
                       dp[i][j]=0;
                   }                    
                }
                if(dp[i][j] > max_square){
                    max_square = dp[i][j];
                }
            }
        }
      return max_square*max_square;        
    }
};
骑士游戏
class Solution {
 public:
    int calculateMinimumHP(vector<vector<int>>& dungeon) {
        int m=dungeon.size();
        int n=dungeon[0].size();
        //dp[m][n]表示进入房间[m][n]所需要的最小血量HP
        int dp[m][n];                
        //从终点出发,先假设到达终点时所需要的最小血量,再依次求得为了继续前进到下一步所需的最小HP
        for(int i= m-1;i>=0;--i){
            for(int j=n-1;j>=0;--j){
                //dp[i][j] 代表从i,j处出发到达终点需要的最少血量                
                if(i==m-1 && j==n-1){
                    //本身就在终点处,则进入时的血量 HP+dungeon[i][j] >= 1 && HP>=1则只要剩余1即可
                    //即: HP>=1-dungeon[i][j] && HP>=1
                    //满足以上不等式的HP的最小解为 max(1-dungeon[i][j],1);
                    dp[i][j] = max(1-dungeon[i][j],1);
                }else if(i== m-1 && j<n-1){
                    //只能向右边走
                    //进入本格子血量 HP+dungeon[i][j] >= DP[i][j+1] && HP >= 1 
                    //               -->> 满足以上的不等式的最小的HP是 max(DP[i][j+1]-dungeon[i][j],1)
                    dp[i][j] =max(dp[i][j+1]-dungeon[i][j],1); 
                }else if(i<m-1 && j==n-1){
                    //只能向下走
                    dp[i][j] =max(dp[i+1][j]-dungeon[i][j],1); 
                }else{
                    //此时既可以向下走,也可以向右边走,根据以上两种情况
                    //向右走进入当前位置至少需要血量: 
                    int r  =max(dp[i][j+1]-dungeon[i][j],1); 
                    //向下走为:
                    int d =max(dp[i+1][j]-dungeon[i][j],1); 
                    
                    //从两个方向中选择对血量要求低的
                    dp[i][j] = min(r,d);
                }
            }
        }
                return dp[0][0];
    }
        
};

序列DP

股票系列

所有套路一致: 参照文章 团灭股票买卖问题

只能交易一次333
交易任意次数333
只能交易2次
只能交易K次333
交易之间需要间隔一天333
交易收取交易费333

字符串匹配

基本套路
字符串匹配的套路基本是建立一个二维DP,分别代表S1到达位置i 和S2到达位置j时的匹配状况。
再根据末尾元素是否相等,是否是特殊字符等分类递推。

最长公共子串333

状态定义:dp[i][j] 代表s1 s2 中到第i-1结尾和第j-1结尾的子串的最长公共子串。i == 0或 j == 0代表空串。
递推公式:

dp[i+1][j+1]= dp[i][j]+1 (当s[i]==s[j])
dp[i+1][j+1]=max{dp[i][j],dp[i][j+1],dp[i+1][j]} (当s[i]!=s[j])
class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        if(text1.empty()||text2.empty()) return 0;
        
        vector<vector<int>>dp(text1.size()+1,vector<int>(text2.size()+1,0));        
        for(int i=0;i<text1.size();++i){
            for(int j=0;j<text2.size();++j){              
                if(text1[i]==text2[j]){
                    dp[i+1][j+1]=dp[i][j]+1;
                }else{
                    dp[i+1][j+1]=max(dp[i][j],dp[i+1][j]);
                    dp[i+1][j+1]=max(dp[i+1][j+1],dp[i][j+1]);
                }
            }
        }
        return dp[text1.size()][text2.size()];
    }
};
编辑距离333

思路类似最长公共子序列,dp[i][j]表示字符串s1 s2的i+1,j+1结尾的子串的最小编辑距离.i == 0 || j==0 表示空串。再判断s1[i] 与s[j]是否相同。

class Solution {
public:
    int minDistance(string word1, string word2) {
        if(word1.empty()||word2.empty()) return max(word1.size(),word2.size());
        
        int dp[word1.size()+1][word2.size()+1];
        
        for(int i=0;i<=word1.size();++i){
            for(int j=0;j<=word2.size();++j){
                if(i==0 && j==0) dp[i][j]=0;
                else if(i==0){
                    dp[i][j]=dp[i][j-1]+1;
                }else if(j==0){
                    dp[i][j]=dp[i-1][j]+1;
                }else{
                    if(word1[i-1]==word2[j-1]){
                        dp[i][j]=min(dp[i-1][j-1],min(dp[i-1][j]+1,dp[i][j-1]+1));
                    }else{
                        dp[i][j]=min(dp[i-1][j-1]+1,min(dp[i-1][j]+1,dp[i][j-1]+1));
                    }
                }
            }
        }
        return dp[word1.size()][word2.size()];
    }
};
通配符匹配333

类似于72,以,dp[i][j]表示字符串s1 s2的i+1,j+1结尾的子串的匹配状态。
注意边界条件的初始化

class Solution {
public:
    bool isMatch(string s, string p) {        
       if(p.size() == 0){
          return s.empty();
       }
       
       //注意dp的第0行与第0列代表s或p为空串的边界情况 
        vector<vector<bool>>dp(s.size()+1,vector<bool>(p.size()+1,false));             
        //初始化都为空串的边界条件
        dp[0][0]=true;
             
        //初始化s为空串状态的边界条件   
        for(int j=0;j<p.size();++j){
            //注意数组索引!!!
            if(p[j] == '*'){
                dp[0][j+1] = dp[0][j];
            }else{
                dp[0][j+1] =false;
            }
        }
         
         //更新状态方程       
        for(int i=0;i<s.size();++i){
            for(int j=0;j<p.size();++j){
                if(p[j] == '*'){
                    dp[i+1][j+1] = (dp[i][j]||dp[i+1][j]||dp[i][j+1]);
                }else if((p[j] =='?') || (p[j]==s[i])){
                    dp[i+1][j+1] = dp[i][j];
                }
            }
        }
         return dp[s.size()][p.size()];        
    }
};
正则匹配

类似72,注意以* 符号进行分类,当前字符为是 * 时,分为匹配0次与1次的情况。
其中匹配1次时包括s[i-1]与p[j-1]已经匹配以及 s[i-1]与p[j]已经匹配两种情况。

class Solution {    
public:
    bool isMatch(string s, string p) {        
        int m=s.size();
        int n=p.size();
        //dp[i][j]代表s0-i 与p0-j是否匹配
        vector<vector<bool>> dp(m+1,vector<bool>(n+1,false));
        dp[0][0]=true;

        //初始化s为空串的边界情况
        for(int i=0;i<n;++i){
            if(p[i]=='*' && i-1>=0)dp[0][i+1] = dp[0][i-1];
            else dp[0][i+1]=false;
        }
        
        for(int i=0;i<m;++i){
            for(int j=0;j<n;++j){
                //当前模式字符不是*
                if(p[j]!='*'){
                   dp[i+1][j+1] = dp[i][j] && (s[i]==p[j] || p[j]=='.'); 
                }else{
                   //1.当前*前字符重复0次
                   bool r1 = j-1>=0 && dp[i+1][j-1];
                   //2.当前*前字符可匹配一次
                   bool r2= j-1>=0 && (dp[i][j] || dp[i][j+1])&&(s[i]==p[j-1] || p[j-1]=='.');
                   dp[i+1][j+1] =r1||r2;
                }
            }
        }
        return dp[m][n];        
    }
};
相间字符串
class Solution {    
public:
    bool isInterleave(string s1, string s2, string s3) {      
        if(s1.size()+s2.size() != s3.size()) return false;

        int m=s1.size();
        int n=s2.size();
        int dp[m+1][n+1];        
        //初始化边缘条件 s1为空
        for(int i = 0 ; i<n+1;++i){
            if(s2.substr(0,i) == s3.substr(0,i)){
                dp[0][i] = 1;
            }else{
                dp[0][i]=0;
            }
        }
        //初始化边缘条件  s2为空           
        for(int i = 0 ; i<m+1;++i){
            if(s1.substr(0,i) == s3.substr(0,i)){
                dp[i][0] = 1;
            }else{
                dp[i][0]=0;
            }
        }
       
        for(int i=1; i<m+1;++i){
            for(int j=1;j<n+1;++j){
                bool cmp1=false,cmp2=false;
                if(s1[i-1] == s3[i+j-1]){
                   cmp1 = dp[i-1][j]; 
                }
                if(s2[j-1] == s3[i+j-1]){
                   cmp2 = dp[i][j-1]; 
                }                
                dp[i][j] = cmp1||cmp2;          
            }
        }
        return dp[m][n];
    }
};
不同字符串子序列333
class Solution {
public:
    int numDistinct(string s, string t) {
        if(t.empty()) return 1;
        
        if(s.size() < t.size()) return 0;
                
        int m=s.size();
        int n=t.size();
        unsigned int dp[m+1][n+1];
        
        for(int i=0 ;i<n+1;++i){
            dp[0][i]=0;
        }
        
        for(int i=0;i<m+1;++i){
           dp[i][0] = 1;
        }
        
        for(int i=1;i<m+1;++i){
            for(int j=1;j<n+1;++j){
                if(i<j){
                    dp[i][j] = 0;
                }else{
                   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[m][n];        
    }
};

区间DP

最长回文子串333

注意子串需要判断两端之间的子串是否是回文串

class Solution {
public:
    string longestPalindrome(string s) {
        if(s.size() <=1) return s;
        if(s.size() == 2 && s[0]==s[1]) return s;
        if(s.size()==3 &&s[0]==s[2]) return s;
        
        int m=s.size();
        bool dp[m][m];
        for(int i=0;i<m;++i){
            for(int j=0;j<m;++j){
                dp[i][j] = false;
            }
        }
        
        int max_len =0;
        int max_s_start =0;
        for(int i=0;i<m;++i){
            for(int j=i;j>=0;--j){
                if(i==j) {
                    dp[j][i]=true;
                }else if(s[i] == s[j]){
                    if(j+1 == i) dp[j][i] =true;
                    else if(j+1<=i-1 && dp[j+1][i-1]) dp[j][i]=true;
                    else dp[j][i]=false;
                }else{
                    dp[j][i]=false;
                }
                if(dp[j][i]){
                    if(i-j+1 > max_len){
                        max_len =i-j+1;
                        max_s_start =j;
                    }
                }
                
            }
        }
        return s.substr(max_s_start,max_len);
    }
};
最长回文子序列333

dp[i][j]表示s[i]开头 s[j]结尾的子串
从而通过判断s[i]与s[j]是否相等,找到dp[i][j] 与dp[i+1][j-1]关系或与dp[j+1][i],dp[j][i-1]关系

class Solution {
public:
    int longestPalindromeSubseq(string s) {
        if(s.size()<=1) return s.size();        
        int dp[s.size()][s.size()];        
         for(int i=0;i<s.size();++i){
            for(int j=i;j>=0;--j){
                if(i==j){
                    dp[j][i]=1;
                }else{
                    //两端字符相同,从里侧子串推导,注意j+1==i的情况
                    if(s[i]==s[j]){
                        if(j+1<=i-1) dp[j][i]=dp[j+1][i-1]+2;
                        else{
                            dp[j][i]=2;
                        }
                    }else{
                      //两端字符不同,则各取一端,取最大值
                        dp[j][i]=max(dp[j+1][i],dp[j][i-1]);
                    }
                }
            }
        }
        return dp[0][s.size()-1];        
    }
};
打气球

此题的解题关键是每个气球得分是与向邻两侧气球分数有关。所以可以假设气球i为最后一个射破气球。再将气球i分割为左右两个子区间L,R。因此依赖于L R的解。

class Solution {
public:
       //<1>核心要点:
    //想到把某个气球作为最后一个被射击的,从而可以得到一个状态。但是该气球把整个序列化为左右子区间
    //此时应意识到应该以区间作为迭代元素,从小区间逐渐迭代到大区间,从而在以气球划分子区间时,可将子区间结果作为已知量。   
    int maxCoins(vector<int>& nums) {
        
        if(nums.size()==0) return 0;
        if(nums.size()== 1)return nums[0];
        
        int N=nums.size();
        
        //两侧加1,作为边界
        vector<int>temp={1};
        for(auto i:nums){
            temp.push_back(i);
        }
        temp.push_back(1);
        
        //dp[i][j]代表只射击区间[i,j]的最大score
        int dp[N+1][N+1];
        //依次扫描长度为1 2 ...N的区间
        for(int len=1;len<=N;++len){
            //对每种区间长度,生成区间的首尾元素
            for(int start=1;start<=N;++start){
                int end = start+len-1;
                if(end>N) break;                
                //区间只有一个元素,直接射破
                if(start == end){
                    dp[start][end] = temp[start-1]*temp[start]*temp[end+1];
                }else{
                    //依次假设区间某个元素为最后一个被射击,先求其左右子区间
                    //最终得到当前区间最大值
                    dp[start][end]=0;
                    for(int last = start;last<=end;++last){
                        int left_sub_range_score =0;
                        int right_sub_range_score =0;
                        if(last-1>=start) left_sub_range_score=dp[start][last-1];
                        if(last+1<=end) right_sub_range_score=dp[last+1][end];                        
                        //射爆最后一个气球时,由于左右子区间已经被射掉,因此乘以当前区间两边外侧的第一个元素
                        int last_burst_score=temp[start-1]*temp[last]*temp[end+1];
                        dp[start][end] = max(dp[start][end],left_sub_range_score+last_burst_score+right_sub_range_score);
                    }
                }
            }
        }
        
        return dp[1][N];
    }
};

背包DP

博弈DP

参考文章:动态规划之博弈问题

预测胜利者
//采用博弈问题的状态方程解题框架
class Solution{
    public:
     bool PredictTheWinner(vector<int>& nums) {
         if(nums.size()<=1)return true;
                  
         pair<int,int>dp[nums.size()][nums.size()];
         
        for(int j=0;j<nums.size();++j){ 
          for(int i=j;i>=0;--i){ 
            if(i==j){
                dp[i][j].first=nums[i];
                dp[i][j].second=0;
            }else{
                int l=dp[i+1][j].second+nums[i];
                int r=dp[i][j-1].second+nums[j];
                dp[i][j].first=max(r,l);
         
                if(l>r){
                  dp[i][j].second=dp[i+1][j].first;
                }else{
                  dp[i][j].second=dp[i][j-1].first;
                }
            }
          }
        }
         return dp[0][nums.size()-1].first>= dp[0][nums.size()-1].second;
         
     }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【优质项目推荐】 1、项目代码均经过严格本地测试,运行OK,确保功能稳定后才上传平台。可放心下载并立即投入使用,若遇到任何使用问题,随时欢迎私信反馈与沟通,博主会第一时间回复。 2、项目适用于计算机相关专业(如计科、信息安全、数据科学、人工智能、通信、物联网、自动化、电子信息等)的在校学生、专业教师,或企业员工,小白入门等都适用。 3、该项目不仅具有很高的学习借鉴价值,对于初学者来说,也是入门进阶的绝佳选择;当然也可以直接用于 毕设、课设、期末大作业或项目初期立项演示等。 3、开放创新:如果您有一定基础,且热爱探索钻研,可以在此代码基础上二次开发,进行修改、扩展,创造出属于自己的独特应用。 欢迎下载使用优质资源!欢迎借鉴使用,并欢迎学习交流,共同探索编程的无穷魅力! 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值