球球速刷LC之DP框架

DP的本質是巧妙定義狀態,並找到狀態的遞推關係

一、經典

3.三角形最小路徑和
狀態:對於第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());        
    }
};

6.雞蛋掉落

二、股票系列

所有套路一致: 參照文章 團滅股票買賣問題
121 只能交易一次
122 交易任意次數
123 只能交易2次
188 只能交易K次
309 交易之間需要間隔一天
714 交易收取交易費

三、字符串匹配

四、區間DP

五、揹包DP

六、樹形DP

所謂樹形DP即當前問題依賴於左右子樹的最優值,類似於後序遍歷。
124 二叉樹中的最大路徑和

class Solution {
   int  result=0;
    int _maxP(TreeNode*root){        
        int l=0,r=0;
        if(root->left)l=_maxP(root->left);
        if(root->right)r=_maxP(root->right); 
        int sub_max= max(root->val,max(l+root->val,r+root->val));   
        //子樹的最大值 不能同時包括左右子樹與根節點,但是最終要求的值可以包含左右子樹和根節點
        //所以這裏單獨計算一下result的值
        result=max(result,max(sub_max,l+r+root->val));
        return sub_max;
    }
public:
    int maxPathSum(TreeNode* root) {
        if(root==NULL) return 0;
        result=root->val;
        _maxP(root);
        return result;
    }
};

337 打家劫舍 III
將問題分爲偷當前root和不偷。並得到與子樹遞推關係

class Solution{
    //返回位置0爲偷root 返回位置1爲不偷root
    vector<int> _rob(TreeNode*root){
        vector<int> ret={0,0};
        if(root==NULL) return ret;
        
        auto l=_rob(root->left);
        auto r=_rob(root->right);
        //ret[0]爲偷當前樹root,故子樹只能不偷root
        ret[0]=l[1]+r[1]+root->val;
        //ret[1]爲不偷當前root,故左右子樹均可偷或不偷root,選取較大值。
        ret[1]=max(l[0],l[1])+max(r[0],r[1]);
        return ret;
    }
    public:
    int rob(TreeNode*root){
        auto ret=_rob(root);
        return max(ret[0],ret[1]);
    }
};

七、計數DP

96 不同二叉樹數目

class Solution {
   public:
    int numTrees(int n) {
        if(n <= 0 ) return 0;    
        vector<int>dp(n+1,0);
        dp[0]=dp[1]=1; //dp[0]代表空樹
        
        for(int i=2;i<=n;++i){
            for(int j=1;j<=i;++j){ //依次假設1...i爲root節點,則左子樹 右子樹數目分別爲:dp[j-1] dp[i-j]
                dp[i]+=dp[j-1]*dp[i-j];
            }
        }
        return dp[n];
    }     
};

八、博弈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;
         
     }
};

翻轉游戲

292 Nim 遊戲

877 石子游戲

1140 石子游戲 II

井字遊戲

348 判定井字棋勝負

九 生成特定數列

關鍵是生成算子
264 醜陋數

class Solution {   
public:
    int nthUglyNumber(int n) {
        if(n<=0) return 0;
        if(n == 1) return 1;        
        //對於任何一個 ugly 數,必然可以通過該數之前的 ugly數列中的某三個ugly元素 L1 L2 L3 乘以2,3,5得到。        
        //即可以設置三個生成數L1 L2 L3,這三個數分別通過*2 *3 *5生成新的ugly 數。而ugly數列中唯一的新元素是當前三個生成數生成的元素中最小的元素。
        //對於被選中爲當前ugly 數列新元素所對應的 生成數Li,將Li更新爲ugly數列中下一個元素,原因是當前Li所生成的元素已經是最小的新元素,其不可能再生成新元素了。        
        std::vector<int>uglys;
        uglys.push_back(1);
        int L1=0,L2=0,L3=0;        
        while(uglys.size()<n){
            //得到當前L1 L2 L3算子生成元素的最小值,即爲當前數列的新元素
            int new_ugly = min(uglys[L1]*2,min(uglys[L2]*3,uglys[L3]*5));
             uglys.push_back(new_ugly);
            
            //當前元素是由L1算子生成
            if(new_ugly == uglys[L1]*2) ++L1;
            
            //當前元素是由L2算子生成
            if(new_ugly == uglys[L2]*3) ++L2;
            
            //當前元素是由L3算子生成
            if(new_ugly == uglys[L3]*5) ++L3;
         }   
        return uglys[uglys.size()-1];
    }
};

313 超級醜陋數

class Solution {
public:
    int nthSuperUglyNumber(int n, vector<int>& primes) {
        vector<int>numbers={1};
        vector<int>L(primes.size(),0);
               
        while(numbers.size()<n){
          int curr_min = numbers[L[0]]*primes[0];
          for(int i=0; i<L.size();++i){
            curr_min=min(curr_min,numbers[L[i]]*primes[i]);
          }
          numbers.push_back(curr_min);
          for(int i=0;i<L.size();++i){
            if(numbers[L[i]]*primes[i] == numbers.back()){
                L[i]++;
            }
          }
        }
        return numbers.at(n-1);
    }
};

十 網格DP

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章