[LeetCode] 動態規划入門題目


最近接觸了動態規划這個厲害的方法,還在慢慢地試着去了解這種思想,因此就在LeetCode上面找了幾道比較簡單的題目練了練手。

首先,動態規划是什么呢?很多人認為把它稱作一種“算法”,其實我認為把它稱作一種“思想”更為合適;利用動態規划去解決問題,其實就是逐步遞推的過程,與貪心算法不同,動態規划遞推的每一步都要求是當前的最優解(這是很重要的,遞推的正確性依賴的就是這一點);利用動態規划解題時,必須自己定義出來狀態和狀態轉移方程。然而,看上去簡單,做起來卻非常困難,因為解題時的具體形式千差萬別,找出問題的子結構以及通過子結構重新構造最優解的過程很難統一。

經典的動態規划題目有背包問題、硬幣問題等等,可以通過這些題目去理解一下這個東西。

我認為,動態規划最難的就是找出狀態方程。同時,個人認為比較難理解的一點是,懂得“前面每一步都是最優解”這個前提。

廢話不多說,直接看看LeetCode上簡單的動態規划題目。

要注意的是,下面的三題都用到了局部最優和全局最優解法:

1.Jump Game

原題地址:https://leetcode.com/problems/jump-game/description/

解法:

用一個global變量保存到目前為止能跳的最遠距離,用一個local變量保存當前一步出發能跳的最遠距離,這題里面的狀態就是走到每一步時的global[i]值,狀態轉移方程就是global[i] =max{nums[i] + i, global[i-1]}。當然,寫代碼的時候用變量代替數組即可。

class Solution {
public:
    bool canJump(vector<int>& nums) {
      int reach = 0;
      for (int i = 0; i < nums.size() - 1 && reach >= i; i++) {
          reach = nums[i] + i > reach ? nums[i] + i : reach;
      }
      return reach >= nums.size() - 1;
  }
};

 

2.Maximum Subarray

原題地址:https://leetcode.com/problems/maximum-subarray/description/

解法:

這一題要維護兩個變量:global和local,與上面一題一樣,local保存包含當前元素的最大值(局部最優),global保存的是所有情況里面的最大值(全局最優)。假設第i步的local[i]和global[i]已知,那么第i+1步的local[i + 1] = max{ nums[i] + local[i], nums[i + 1] },global[i + 1]  = max{global[i], local[i + 1]}。代碼如下:

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
      int global = nums[0], local = nums[0];
      for (int i = 1; i < nums.size(); i++) {
          local = nums[i] > nums[i] + local ? nums[i] : nums[i] + local;
          global = local > global ? local : global;
      }       
      return global;
  }
};

 

3.Best Time to Buy and Sell Stock

原題地址:https://leetcode.com/problems/best-time-to-buy-and-sell-stock/description/

這道題目有兩種方法,其實都是動態規划:

(1)

class Solution {
public:
   int maxProfit(vector<int>& prices) {
       if (prices.size() == 0) return 0; 
      int maxPrice = prices[prices.size() - 1];
      int res = 0;
      for (int i = prices.size() - 1; i >= 0; i--) {
          maxPrice = max(maxPrice, prices[i]);
          res = max(res, maxPrice - prices[i]);
      }
      return res;     
  }
};

這種解法在這個博客里面講得很詳細:http://www.cnblogs.com/remlostime/archive/2012/11/06/2757434.html

(2)局部最優和全局最優解法:

class Solution {
public:
   int maxProfit(vector<int>& prices) {
      if (prices.size() == 0) return 0;
      int local = 0, global = 0;
      for (int i = 1; i < prices.size(); i++) {
          local = max(0, local + prices[i] - prices[i - 1]);
          global = max(local, global);
      }
      return global;   
  }
};
local = max(0, local + prices[i] - prices[i - 1])這一句,我一開始在考慮:為什么不寫成local = max(local, local + prices[i] - prices[i - 1])呢?
后來想了一下,因為假如這樣寫,有可能得到的就不是包含當前元素的局部最優解了。所以,在“局部最優和全局最優解法”里面,永遠不會出現local=local的情況。


4.Minimum Path Sum
原題地址:https://leetcode.com/problems/minimum-path-sum/description/
這道題目不需用到上面的“局部最優和全局最優”解法,只需要每次選出最優的即可。除了邊界的元素,其他元素的最優都是 min{min[i - 1][j] + grid[i][j],min[i][j - 1] + grid[i][j]
}。
class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int ** min  = new int*[grid.size()];
        for (int i = 0; i < grid.size(); i++) {
            min[i] = new int[grid[i].size()];
        }
        min[0][0] = grid[0][0];
        for (int i = 0; i < grid.size(); i++) {
            for (int j = 0; j < grid[i].size(); j++) {
                if (i == 0 && j == 0) continue;
                else if (i == 0) min[i][j] = min[i][j - 1] + grid[i][j];
                else if (j == 0) min[i][j] = min[i - 1][j] + grid[i][j];
                else min[i][j] = min[i - 1][j] + grid[i][j] < min[i][j - 1] + grid[i][j] ? min[i - 1][j] + grid[i][j] : min[i][j - 1] + grid[i][j] ;
            }
        }
        return min[grid.size() - 1][grid[grid.size() - 1].size() - 1];
        return 0;
    }
};


5.Triangle
地址:https://leetcode.com/problems/triangle/description/

也是一道典型的dp題目,思想跟上面一題差不多:
class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        if (triangle.size() == 1) return triangle[0][0];
        int ** min = new int *[triangle.size()];
        for (int i = 0; i < triangle.size(); i++) {
            min[i] = new int[triangle[i].size()];
        }
        min[0][0] = triangle[0][0];
        int res = INT_MAX;
        for (int i = 0; i < triangle.size(); i++) {
            for (int j = 0; j < triangle[i].size(); j++) {
                if (i == 0 && j == 0) continue;
                else if (j == 0) min[i][j] = min[i - 1][j] + triangle[i][j];
                else if (j == triangle[i].size() - 1) min[i][j] = min[i - 1][j - 1] + triangle[i][j];
                else min[i][j] = min[i - 1][j - 1] + triangle[i][j] < min[i - 1][j] + triangle[i][j] ? min[i - 1][j - 1] + triangle[i][j] : min[i - 1][j] + triangle[i][j];
                if (min[i][j] < res && i == triangle.size() - 1) {
                    res = min[i][j];
                }
            }
        }
        return res;
    }
};
但這道題有趣的地方在於,空間復雜度可以縮小到O(n):我們把這個三角形倒過來看,便能發現可以通過復用一個一維數組來儲存最小值:
class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        vector<int> min = triangle[triangle.size() - 1];
        for (int i = triangle.size() - 2; i >= 0; i--) {
            for (int j = 0; j <= i; j++) {
                min[j] = min[j] < min[j + 1] ? min[j] + triangle[i][j] : min[j + 1] + triangle[i][j];
            }    
        }
        return min[0];
    }
};
 
        

 









免責聲明!

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



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