C++中的動態規划算法及常見題目匯總


什么是動態規划

    • 在面試過程中如果是求一個問題的最優解(通常是最大值或者最小值),並且該問題能夠分解成若干個子問題,並且子問題之間好友重疊的更小子問題,就可以考慮用動態規划來解決這個問題。
  • 動態規划的分類

      大多數動態規划問題都可以被歸類成兩種類型:優化問題和組合問題

    • 優化問題

      優化問題就是我們常見的求一個問題最優解(最大值或者最小值)

    • 組合問題  

      組合問題是希望你弄清楚做某事的數量或者某些事件發生的概率

    • 兩種不同動態規划解決方案
      • 自上而下:即從頂端不斷地分解問題,知道你看到的問題已經分解到最小並已得到解決,之后只用返回保存的答案即可
      • 自下而上:你可以直接開始解決較小的子問題,從而獲得最小的解決方案。在此過程中,你需要保證在解決問題之前先解決子問題。這種方法叫做表格填充法。        
  • 常見的動態規划例子
  1. 裴波那契數列
  2. 把數字翻譯成字符串
  3. 最佳觀光組合
  4. 買賣股票的最佳時機
  5. 最大子序和
  6. 區域和檢索-數組不可變
  7. 按摩師
  8. 打家劫舍
  9. 最小化費爬樓梯
  10. 三步問題
  11. 猜數字大小
    •  1. 裴波那契數列

      裴波那契數列就是典型的組合問題,要求出做某事的數量或者概率

 

      • 問題分析:對於題目中的青蛙爬樓梯問題,初試情況下是只有一級台階時,只有一種跳法,只有兩級台階時,有兩種跳法,當有n級台階時,設n級台階的跳法總數是f(n),如果第一步跳一級台階,則和剩下的n-1級台階的跳法是一樣的,如果第一級跳兩級台階,則和剩下的n-2級台階的跳法是一樣的,因此最終n級台階的跳法是f(n)=f(n-1)+f(n-2),即其是可以被分解為更小的子問題的,下面我們以求解f(10)為例來分析遞歸的過程

                                 

        我們從這張圖中不難發現,在這棵樹中有很多節點都是重復的,而且重復節點會隨着n的增大而急劇增大,因此我們采用自頂向下的方式會有很低的效率,因此我們采用自下而上的方法,首先根據f(1)和f(2)計算出f(3),再根據f(2)和f(3計算出f(4),以此類推求出f(n)

        實現的代碼如下

 1 int jumpFloor(int number) {
 2         if(number<0)
 3             return 0;
 4         else if(number==0||number==1||number==2)
 5             return number;
 6         else
 7         {
 8             int result=0;
 9             int f1=1;
10             int f2=2;
11             for(int i=3;i<=number;++i)
12             {
13                 result=f1+f2;
14                 f1=f2;
15                 f2=result;
16             }
17             return f2;
18         }
19     }
      • 矩形覆蓋問題

        

        • 問題分析:由於2*1的小矩形可以橫着放,也可以豎着放,當n=1時,其只有一種方式,f(1)=1,n=2時,有兩種覆蓋方式f(2)=2如圖

          

          當要構成2*n的大矩形時,如果第一個小矩形豎着放,則其和后面n-1個小矩形的方法相等,如果第一個小矩形橫着放,則第二個小矩形也只能橫着放,即上圖右邊的方法,因此其和后面n-2個小矩形的放法相等。f(n)=f(n-1)+f(n-2),也是一個裴波那契數列。代碼如上

    • 2. 把數字翻譯成字符串 

     

 

 

 

      • 問題分析:由於是求數字的翻譯方法,因此這是一個組合問題,如果對於這種數字問題,一般將其轉換成字符串來進行求解,對於如果第一個位數是1,則其一定有兩種解法,即可以翻譯成一個數字或者兩個數字,即B[i+1]=B[i]+B[i-1],如果第一個位數是1,如果第二位數大於0,小於5,則有兩種翻譯方法,B[i+1]=B[i]+B[i-1],其他情況即只有一種翻譯方法:B[i+1]=B[i]
      • 我們首先看下面一個圖

        

        我們將一個字符串翻譯分解成很多子問題來進行求解

      • 代碼參考
class Solution {
public:
    int B[70]={1,1};
    int translateNum(int num) {
        if(num<0)
            return 0;
        string nums=to_string(num);
        int numsize=nums.size();
        for(int i=1;i<numsize;++i)
        {
            if(nums[i-1]=='1')
                B[i+1]=B[i]+B[i-1];
            else if(nums[i-1]=='2')
            {
                if(nums[i]>='0'&&nums[i]<='5')
                    B[i+1]=B[i]+B[i-1];
                else
                    B[i+1]=B[i];
            }
            else
                B[i+1]=B[i];
        }
        return B[numsize];
    }
};

                

 

      •  問題分析:這道題當然可以用暴力法進行求解,但是這樣的時間效率過低,因此考慮其他的方法。由於兩者之間的得分為A[i]+A[j]+i-j,也可以將其寫成A[i]+i+A[j]-j,當遍歷到j時A[j]-j的值是不變的,因此最大化A[i]+A[j]+i-j的值就等價於求[0,j-1] 

    中A[i]+i的最大值mx.即景點j的答案為mx+A[j]-j,而mx的值只要從后枚舉j的時候維護既可以

       

class Solution {
public:
    int maxScoreSightseeingPair(vector<int>& A) {
        if(A.empty())
            return 0;
        int n=A.size();
        int fn=0;
        int tn=A[0]+0;
        for(int i=1;i<n;++i)
        {
            fn=max(fn,tn+A[i]-i);
            tn=max(tn,A[i]+i);
        }
        return fn;
    }
};

  

     

 

      •  問題分析,由於第i天的最大收益=max[前i-1天的最大收益,第i天的最大收益],因此其是一個動態規划問題,詳細分析見下圖

                               

      • 代碼如下
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if(prices.empty())
            return 0;
        vector<int> result(prices.size(),0);
        int minprice=prices[0];
        for(int i=1;i<prices.size();++i)
        {
            //核心思路是,前i天的最大收益=max[前i-1天的最大收益,第i天的最大收益]
            result[i]=max(result[i-1],prices[i]-minprice);
            if(prices[i]<minprice)
                minprice=prices[i];
        }
        return result[prices.size()-1];
    }
};

    

 

      • 解題分析:要找到具有最大和的連續子數組,我們有兩種思路,
      • 思路一:舉例分析數組的規律。從頭到尾累加數組中的每個數字,初始化為0,第一步加上第一個數字,此時和為1,第二步加上第二個數字-1,此時和變成了-1;第三步加上數字3,我們注意到此前累加的和為-1,小於0,如果用-1+3,得到的和為2,小於3,也就是說,從第一個數字開始的數組和會小於第三個數字開始的子數組的和。因此,我們不用考慮從第一個數組開始的子數組,之前累加的和也被拋棄。此時我們從第三個數字開始累加,發現得到的和是3,第四步加10,得到13.。。
      • 思路二,利用動態規划的思想   

         

  

 1 class Solution {
 2 public:
 3     int maxSubArray(vector<int>& nums) {
 4         if(nums.empty())
 5             return 0;
 6         int fn=nums[0];
 7         int result=nums[0];
 8         for(int i=1;i<nums.size();++i)
 9         {
10             fn=max(fn+nums[i],nums[i]);
11             result=max(result,fn);
12         }
13         return result;
14     }
15 };

 

    • 6. 區域和檢索,數組不可變
      • 問題描述

        

      • 解題分析:對於給定整數數組nums中,要求出數組從索引i到j范圍內的總和,包含i,j兩點我們可以直接求到(0-j的總和)-(0-(i-1)的總和)      

                     

      代碼分析

class NumArray {
public:
    vector<int> res;
    NumArray(vector<int>& nums) {
        int n=nums.size();
        if(n>0)
        {
            vector<int> dp(n+1);
            dp[0]=0;
            for(int i=1;i<=n;++i)
            {
                dp[i]=dp[i-1]+nums[i-1];                
            }
            res=dp;
        }
    }  
    int sumRange(int i, int j) {
        return res[j+1]-res[i];
    }
};

/**
 * Your NumArray object will be instantiated and called as such:
 * NumArray* obj = new NumArray(nums);
 * int param_1 = obj->sumRange(i,j);
 */
    •  7. 按摩師問題
      • 題目描述

        

 

      • 題目分析

             

      由於我們每一次遞歸,都只用到了dp[i],dp[i-1],dp[i-2]三個位置,則dp數組有點浪費,因此可以采用滾動數組的思想來進行優化

      滾動數組實際上實在動態規划中一種節省空間的方法。由於動態規划是一個自底向上擴展的過程,我們常常需要用到的是連續的解,前面的解往往可以舍去,因此利用滾動數組優化是很有效的,利用滾動數組在N很大的情況下可以達到壓縮存儲的作用

                           

 代碼

class Solution {
public:
    int massage(vector<int>& nums) {
        if(nums.empty())
            return 0;
        int ppre=0,pre=0,now=0;
        for(int i=1;i<=nums.size();++i)
        {
            ppre=pre;
            pre=now;
            now=max(pre,ppre+nums[i-1]);
        }
        return now;
    }
};
    •  8. 打家劫舍
      • 題目描述

                       

 

      解法同按摩師

    • 9. 最小花費爬樓梯
      • 問題描述

                       

      • 問題解法     

                

      • 問題代碼
class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int size=cost.size();
        vector<int> mincost(size);
        mincost[0]=0;
        mincost[1]=min(cost[0],cost[1]);
        for(int i=2;i<size;++i)
        {
            mincost[i]=min(mincost[i-1]+cost[i],mincost[i-2]+cost[i-1]);
        }
        return mincost[size-1];
    }
};
    • 10. 三步問題
      • 問題描述

                        

 

      • 問題分析

        由於每次能夠走一步,兩步或者三步,假設n階台階的總方法是f(n),則f(1)=1,f(2)=2,f(3)=4 ,當有n級台階的時候,如果第一級台階走1步,則和后面的n-1級台階一樣,如果第一級台階走2步,則和后面的n-2級台階一樣,如果第一級台階走3步,則和后面的n-3級台階一樣,即其表達式為f(n)=f(n-1)+f(n-2)+f(n-3),類似於裴波那契數列

                         

      因此,直接調用遞歸會有很多重復的計算,因此我們采用自頂而下的思想進行實現

class Solution {
public:
    int waysToStep(int n) {
        if(n<3)
            return n;
        long int first=1,second=2,third=4;
        long int temp;
        while(n>3)
        {
            temp=third;
            third=(first+second+third)%1000000007;
            first=second;
            second=temp;
            --n;
        }
        return third;
    }
};

 

11. 猜數字大小2

  • 題目描述

  

  • 解題分析

  •  代碼參考
 1 class Solution {
 2 public:
 3     int getMoneyAmount(int n) {
 4         if(n==1)
 5             return 0;
 6         //定義矩陣
 7         int dp[n+1][n+1];
 8         //初始化
 9         for(int i=0;i<=n;++i)
10         {
11             for(int j=0;j<=n;++j)
12                 dp[i][j]=INT_MAX;
13         }
14         //定義基礎值dp[i][i]
15         for(int i=0;i<=n;++i)
16             dp[i][i]=0;
17         //按列填充,從第二列開始
18         for(int j=2;j<=n;++j)
19         {
20             //按行來,從下往上,因為填充的順序是從下往上的
21             for(int i=j-1;i>=1;--i)
22             {
23                 //算除了兩端的每一個分割點
24                 for(int k=i+1;k<=j-1;++k)
25                 {
26                     dp[i][j]=min()
27                 }
28             }
29 
30         }
31 
32     }
33 };

 

          

 

        

 

         

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM