動態規划(DP)算法


參考https://blog.csdn.net/libosbo/article/details/80038549

 

動態規划是求解決策過程最優化的數學方法。利用各個階段之間的關系,逐個求解,最終求得全局最優解,需要確認原問題與子問題、動態規划狀態、邊界狀態、邊界狀態結值、狀態轉移方程

 

以下每個例題,注意分析迭代關系是怎么找到的

一、爬樓梯leetcode70:

You are climbing a stair case. It takes n steps to reach to the top.

Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?

Note: Given n will be a positive integer.

Example 1:

Input: 2
Output: 2
Explanation: There are two ways to climb to the top.
1. 1 step + 1 step
2. 2 steps

Example 2:

Input: 3
Output: 3
Explanation: There are three ways to climb to the top.
1. 1 step + 1 step + 1 step
2. 1 step + 2 steps
3. 2 steps + 1 step

方法一:利用n個樓梯的步數,與n-1還有n-2之間的關系可以退出,f(n)==f(n-1)+f(n-2),相當於是直接考慮為n-1再上一步,和n-2直接上兩步,不能考慮n-2有兩種走法(一步一步,和一次兩步,一步一步的會和n-1中的重復,導致算多了),最后不斷的迭代直至可以n==1或者n==2,可以直接求出結果。

這個方法相當於是根據各個階段之間的關系,列出迭代關系,並且寫出臨界解,從而結束遞歸的過程,否則將一直遞歸下去(所有的遞歸都是如此,如果沒有邊界條件提前結束遞歸,遞歸將不會停止)

這個時間復雜度是2^n相當於是一顆二叉樹來着,leetcode顯示time limit exceed

1 int climbStairs(int n) {
2         if(n==1||n==2){
3             return n;
4         }
5         return climbStairs(n-1)+climbStairs(n-2);    
6    }

 

方法二:利用迭代來實現尾遞歸

由於方法一是利用了尾遞歸來實現算法,考慮采用迭代來實現遞歸,並且遞歸本身算法復雜度是要遠遠大於其對應的迭代循環算法復雜度的,所以考慮利用迭代來減少時間復雜度。兩種方法的差別在於遞歸是從上往下算,迭代是從下往上算。

 1 class Solution {
 2 public:
 3     int climbStairs(int n) {
 4         vector<int>iteration(n+1,0); //initializition
 5         iteration[1]=1;
 6         iteration[2]=2;
 7         int i=3;
 8         while(i<n+1){
 9             iteration[i]=iteration[i-1]+iteration[i-2];
10             i++;
11         }
12         return iteration[n];  
13     }
14 };

 

二、搶劫犯問題leetcode198

You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security system connected and it will automatically contact the police if two adjacent houses were broken into on the same night.

Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.

題目分析:這個題目的分析關鍵在於DP算法中狀態轉移方程的求解,也就是求解是迭代關系,可以發現對於第i個房間而言,如果不搶劫第i個房間那么就是i-1個的搶劫數目,如果搶劫第i個房間那么就是一定不能搶劫i-1個房間,相當於是i-2個房間的搶劫數目,兩者時間的最大值即可,從而成功得出迭代關系。

 

意自身在分析問題時的錯誤:

一:在無法的出迭代關系的情況下,沒有考慮根據題意對可能的情況進行分類,注意兩個題目都是進行了分類的討論,才得以順利的得出迭代關系,並且盲目的理解為迭代關系一定是兩者之間的和,沒有考慮到最大值得情況。

二:在考慮迭代關系時一定要思考如何引入第i-1個和第i-2個問題的解,這道題就是通過分類討論,成功剝離出了i-1和i-2的情況;迭代關系的另一個要素是如何把i與i-1和i-2之間的關系找到

三:在考慮迭代關系時一定把i考慮成足夠大,因為在代碼實現過程中i很小的情況是直接給出的,直接賦值的(對於有dp數組而言),i很小的情況下只是考慮為邊界條件,作為循環的起始或者是迭代的結束。所以考慮迭代關系時一定不要具體化i而是直接假設i足夠大去考慮。

 

求取迭代關系的步驟:

1、根據題意分類討論,分類討論一定要達到引入i-1和i-2的解

2、挖掘i和i-1還有i-2之間的關系

3、邊界條件確認

 

方法一、使用迭代法

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

方法二、使用遞歸算法

class Solution {
public:
    int rob(vector<int>& nums) {
        int size=nums.size();
        if(size==0) return 0;
        if(size==1) return nums[0];
        if(size==2) return max(nums[0],nums[1]);
        vector<int>a1(nums.begin(),nums.end()-1);
        vector<int>a2(nums.begin(),nums.end()-2);
        return max(rob(a1),rob(a2)+nums[size-1]);
     
    }
};

 

可以發現這種方法再次出現了time limit exceed,時間復雜度是O(2^n),以后不用再考慮遞歸的DP算法了,直接使用迭代,時間復雜度降低很多

 

以上兩道例題都是相似的,接下來的例題稍有不同,要重點理解。

 

三、最大子段和leetcode53(重點理解)

Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.

注意自身在分析問題時的錯誤:

       在分析問題的時候沒有靈活變通,直接考慮前i個連續子數組數組的最大值,將無法進行分類討論,無法得到遞歸關系,並且在考慮遞歸關系時也是直接考慮了i與i-1還有i-2之間的關系,其實可以考慮為i與i-1的關系即可,只要是一種可以迭代出所有情況的關系即可。在不能夠得出迭代關系的時候需要變通的考慮,改變dp數組的意義,不需要一步到位,只要保證可以通過dp數組得到最后的結果即可。

code:dp數組表示的是以第i個元素結尾的連續子數組的最大值,最后再尋找dp的最大值

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

 

注意,這里判斷dp[I-1]的正負來確定迭代關系(這是與之前兩道例題都不同的地方),仔細理解根據num[i]正負來確定迭代關系的區別以及孰是孰非。

 

四、找零錢和leetcode322(非常經典的DP算法,稍復雜)——還未完全理解。。。

You are given coins of different denominations and a total amount of money amount. Write a function to compute the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1.

Example 1:
coins = [1, 2, 5], amount = 11
return 3 (11 = 5 + 5 + 1)

Example 2:
coins = [2], amount = 3
return -1.

Note:
You may assume that you have an infinite number of each kind of coin.

 

 1 class Solution {
 2 public:
 3     int coinChange(vector<int>& coins, int amount) {
 4         vector<int>dp(amount+1,amount+1);
 5         dp[0]=0;
 6         for(int i=1;i<=amount;i++){
 7             for(int m:coins){
 8                 if(m<=i) dp[i]=min(dp[i],dp[i-m]+1);
 9             }            
10         }
11         if(dp.back()==amount+1) return -1;
12             else return dp.back();
13     }
14 };

 

五、三角形——還未完全理解。。。

Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent numbers on the row below.

For example, given the following triangle

[
     [2],
    [3,4],
   [6,5,7],
  [4,1,8,3]
]

The minimum path sum from top to bottom is 11 (i.e., 2 + 3 + 5 + 1 = 11).

 

這道題目類似於第三題,都是對dp數組的意義進行轉換,從而以退為進解決問題。

 

 1 class Solution {
 2 public:
 3     int minimumTotal(vector<vector<int>>& triangle) {
 4         int size=triangle.size();
 5         vector<vector<int>>dp(size,vector<int>(size,INT_MAX));
 6         dp[0][0]=triangle[0][0];
 7         for(int i=1;i<size;i++){
 8             for(int j=0;j<triangle[i].size();j++){
 9                 if(j==0) dp[i][j]=dp[i-1][j]+triangle[i][j];
10                 if(j==triangle[i].size()-1) dp[i][j]=dp[i-1][j-1]+triangle[i][j];
11                 if(j!=0&&j!=triangle[i].size()-1) dp[i][j]=min(dp[i-1][j-1]+triangle[i][j],dp[i-1][j]+triangle[i][j]);              
12             }
13         }
14         return *min_element(dp[size-1].begin(),dp[size-1].end());
15     }
16 };

 


免責聲明!

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



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