首页 > 代码库 > 【Leetcode】动态规划问题详解(持续更新)

【Leetcode】动态规划问题详解(持续更新)

1、动态规划算法步骤(Dynamic Programming)

动态规划算法一般用来求解最优化问题,当问题有很多可行解,而题目要求寻找这些解当中的“最大值”/“最小值”时,通常可以采用DP。
动态规划算法与分治法相似,都是通过组合子问题的解来求解原问题。所不同的是,动态规划应用于子问题重叠的情况,在递归求解子问题的时候,一些子子问题可能是相同的,这种情况下,分治法会反复地计算同样的子问题,而动态规划对于相同的子问题只计算一次。

动态规划算法的设计步骤
1、刻画最优解的结构特征(寻找最优子结构)
2、递归地定义最优解的值(确定递归公式)
3、计算最优解的值(有两种方法:带备忘录自顶向下法、自底向上法)
4、利用计算出的信息构造一个最优解(通常是将具体的最优解输出)

2、leetcode上适合用DP求解的问题

题目OJ地址目录  

Triangle

 
https://oj.leetcode.com/problems/triangle/
 3.1
Maximum Subarray
https://oj.leetcode.com/problems/maximum-subarray/
3.2

Palindrome Partitioning II

 
https://oj.leetcode.com/problems/palindrome-partitioning-ii/
3.3
Minimum Path Sum
https://oj.leetcode.com/problems/minimum-path-sum/
3.4
Maximal Rectangle
https://oj.leetcode.com/problems/maximal-rectangle/
3.5

Interleaving String

 
https://oj.leetcode.com/problems/interleaving-string/
3.6
Edit Distance
https://oj.leetcode.com/problems/edit-distance/
3.7

Decode Ways

 
https://oj.leetcode.com/problems/decode-ways/
3.8

Best Time to Buy and Sell Stoc

   I&II&III
https://oj.leetcode.com/problems/best-time-to-buy-and-sell-stock/
3.9

Scramble String

 
https://oj.leetcode.com/problems/scramble-string/
3.10

Distinct Subsequences

 
https://oj.leetcode.com/problems/distinct-subsequences/
3.11
Word Break I&II
https://oj.leetcode.com/problems/word-break/
3.12







3、leetcode相关题目

3.1 Triangle

3.1.1 题目

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).

Note:
Bonus point if you are able to do this using only O(n) extra space, where n is the total number of rows in the triangle.

3.1.2 分析

题目大意从最顶端往下走,寻找和最小的路径。
显然,从最顶端往下走有很多条路径可以走,但是每一条路径的sum不一样,题目要求sum最小,属于最优化问题,考虑动态规划。

如果用暴力破解,复杂度如何?观察题目所给的三角形,对于每个“节点”,往下都有两条路可以走,比如2可以往3、4走,3可以往6、5走,4可以往5、7走.......类似于二叉树,如果有n行,则一共有路径2^n条,时间复杂度O(2^n)。

动态规划的话,时间复杂度可以降到O(n^2)。下面按照动态规划的步骤来分析这道题:
#1 最优解的结构特征
 第一个步骤要搞清楚怎么将问题分解为更小的子问题。上面已经提到,“2”往下走都有两条路可以选择,分别是“3”、“4”,因此从2出发的最优路径,其实就是从“3”出发的最优路径、从“4”出发的最优路径  这两者中sum最小的一条。

#2 确定递归求解公式
用f(i,j)表示从点(i,j)出发的最优路径的sum,根据上面分析,易得递归公式 :f(i,j)=min{f(i+1,j),f(i+1,j+1)}+Val(i,j)
Val(i,j)表示点(i,j)的值。

#3 根据递归公式求解
分别用“自底向上”、“带备忘录的自顶向下”两种方法,见3.1.3代码

3.1.3 参考代码

自底向上法,注意到f(0,0)取决于f(1,0)、f(1,1);  f(1,0)又取决于f(2,0)、f(2,1)......所以从最底层开始计算是比较自然的做法,而最底层的最短路径和就是节点本身的值,因此实际上是从倒数第二层开始计算。
 int minimumTotal(vector<vector<int> > &triangle) {
        int row=triangle.size();    //行,第row行的元素有row个
        vector<vector<int> > f(triangle);
        //用f[m][n]记录从triangle[m][n]出发到叶子节点的最短路径和。也可以直接用triangle代替f,但会改变triangle
        
        for(int x=row-2;x>=0;x--)
           for(int y=0;y<=x;y++)
              f[x][y]=min(f[x+1][y],f[x+1][y+1])+triangle[x][y];
        return f[0][0];
    }

带备忘录的自顶向下法
class Solution {
public:
    int minimumTotal(vector<vector<int> > &triangle) {
        int row=triangle.size();    //行
        vector<vector<int> > f(triangle);   //f[m][n]表示从triangle[m][n]出发到叶子节点的最短路径和
        for(int m=0;m<row-1;m++)
           for(int n=0;n<=m;n++)
              f[m][n]=INT_MAX;         //与自底向上的方法不同,备忘录法必须将其初始化为标识值,以便“查找备忘录”
	f[row-1]=triangle[row-1];       //最后一行保持原值
        return dp(0,0,triangle,f);      //从根出发
    }
private:
   int dp(int x,int y,vector<vector<int> > &triangle,vector<vector<int> > &f){
        if(f[x][y]!=INT_MAX) return f[x][y];   //查找备忘录,如果已经计算过,直接返回,避免重复计算
        f[x][y]=min(dp(x+1,y,triangle,f),dp(x+1,y+1,triangle,f))+triangle[x][y];
        return f[x][y];
   }
};


【Leetcode】动态规划问题详解(持续更新)