Dynamic Programming

这一类题目,对思考要求比较多。凡是要求思维的问题,一定是有迹可寻,有规律可以依靠的。

希望这一遍整理,能有长足长进。这是算法里都一块大骨头,一定要啃下来。


开始之前先说几句general的心得

dp,是从下往上到(tree),从后往前到(string)推进的。

recursion 正好相反。 从上往下,从前往后, 推进的。

recursion的思路往往是更为容易洞察的,而做dp很容易出现的一个思维陷阱就是,在发现recursion后再做dp,在分析dp推进关系的时候受recursion影响。

这里有一条原则要牢记,

dp在每一步是向前看的。

recursion在每一步是向后看的。


Triangle : **
一个窍门是从下往上推进,就可以不单独处理每行首末位了。
然后就是虽然这里是个三角形。。依然是2D DP
public class Solution {
    public int minimumTotal(List
   
   
    
    
     
     > triangle) {
        if(triangle == null || triangle.size() == 0 || triangle.get(0) == null || triangle.get(0).size() == 0)
            return 0;
        int r = triangle.size();
        int c = triangle.get(r-1).size();
        int[][] dp = new int[r][c];
        for(int i=0; i
     
     
      
      =0; i--){
            for(int j=0; j
      
      
     
     
    
    
   
   

Word Break  : ****

这个题目的DP, 和 Palindrome Partitioning II  放到一起领会。

这俩个问题的共通之处在于,递推关系是内层推外层。

palindrome里很直观  i  <-- i+1    j-1 -->  j

(i,j)  受 (i+1, j-1)影响。

i + 1 决定了 i 要从下向上

j - 1 决定了 j要从左向右

word break 也是一个道理,只不过关系更多种

i .....K..... j

(i , j) 受 (i,k)  与 (k+1,j) 一起影响


凡事外层靠内层推的2维DP, i 从下向上循环,每层里 j 从左到右。

Word Break 这个题也是显然的内层推外层,有些东西太显然反而被忽略。

拿 LeetCode 来说,我们最好要的结果就是LeetCode可以不可以break,固然 i(首char坐标) 最终是在0 位上的,那 i 肯定是从尾扫到头的。

不然你让i 从开始扫,那就成了先有LeetCode  能不能分,再有  eetCode能不能, 显然是不合理的。

所以这个题目也是回扫问题。


这个地方再多说一句,这个题recursion和dp乍一看有点矛盾。recursion明明是上来就是拆LeetCode。而仔细想正是相反才是一致。 DP从subproblem入手往LeetCode这个最终problem推,而recursion则反之,从LeetCode这个最终problem开始,不断的反代自身处理subproblem。 这就是一个从下向上,一个从上向下,所以recursion这是逻辑存在大量重复计算。

public class Solution {
    public boolean wordBreak(String s, Set
    
    
     
      wordDict) {
        if(s==null)
            return false;
        boolean[][] dp = new boolean[s.length()][s.length()];
        for(int i=s.length()-1;i>=0;i--){
            for(int j=i; j
     
     
    
    

先附一个以前做这个题目写的心得体会。

Word Break (Rugged Cross)


Wildcard Matching : ****

这DP之章被提前进行,就是因为这个题。

'?' Matches any single character.
'*' Matches any sequence of characters (including the empty sequence).

The matching should cover the entire input string (not partial).

The function prototype should be:
bool isMatch(const char *s, const char *p)

Some examples:
isMatch("aa","a") → false
isMatch("aa","aa") → true
isMatch("aaa","aa") → false
isMatch("aa", "*") → true
isMatch("aa", "a*") → true
isMatch("ab", "?*") → true
isMatch("aab", "c*a*b") → false
关于 ‘*’ match 任意sequence, 这个sequence 里character不一定是重复的。


DP思路:

1. 纬度定义 

二维DP, dp[i][j] 表示 p截至到i时候,跟s截至到j的匹配是否成功。

2. 递推关系

if( p.charAt(i) != '*')    :     

if(p.charAt(i) == s.charAt(j)   || p.charAt(i) == '?' ) : dp[i][j] = true

if(p.charAt(i) == '*')    :

dp[i][j] = dp[i-1][j] || dp[i-1][j-1] || dp[i-1][j-2] || ... || dp[i-1][0] = dp[i-1][j] || dp[i][j-1]


--------------------------------------------

(i-1 ,0)(i-1 ,1)... (i-1, j-2)(i-1, j-1)(i-1, j)

--------------------------------------------

(i, j-1)(i, j)

--------------------------------------------

3.矩阵初始化

当p取前0位(即空),即为空时, 出了s也不取时候,不论s几个,都无法match, 故第一行除了第一列为true, 其余列都是false

当s取前0位 (即空),第一列按照以下附值。

for(int i=1; i<=p.length();i++){
            if(p.charAt(i-1) == '*' && dp[i-1][0])
                dp[i][0] = true;

        }


这个题做了有四遍了。我总结一下思考上的技巧。 

1.把实际问题和矩阵的问题俩个角度结合起来,相互启发。

2.往往习惯是先从recursion入手,再分析dp。这里要警惕,不要把dp的分析思路让recursion误导。 记住一句话,dp是向前看的。



//improved
    public boolean isMatch(String s, String p) {
        boolean[][] dp = new boolean[p.length()+1][s.length()+1];
        dp[0][0] = true;
        for(int i=1; i<=p.length(); i++){
            if(p.charAt(i-1) == '*')
                dp[i][0] = dp[i-1][0];
        }
        
        for(int i=1; i<=p.length(); i++){
            for(int j=1; j<=s.length(); j++){
                if(p.charAt(i-1) == s.charAt(j-1) || p.charAt(i-1) == '?'){
                    dp[i][j] = dp[i-1][j-1];
                }else{
                    if(p.charAt(i-1) == '*'){
                        dp[i][j] = dp[i-1][j] || dp[i][j-1];
                    }
                }
            }
        }
        return dp[p.length()][s.length()];
    }
    
//older one

public class Solution {
    public boolean isMatch(String s, String p) {
        boolean[][] dp = new boolean[p.length()+1][s.length()+1];
        //初始化矩阵
        dp[0][0] = true;
        for(int i=1; i<=p.length();i++){
            if(p.charAt(i-1) == '*' && dp[i-1][0])
                dp[i][0] = true;
        }
        //初始化结束,开始递归
        for(int i=1;i<=p.length();i++){
            for(int j=1;j<=s.length();j++){
                if(p.charAt(i-1) != '*'){
                    if(dp[i-1][j-1] && (p.charAt(i-1) == s.charAt(j-1) || p.charAt(i-1) == '?')){
                        dp[i][j] = true;
                    }
                }else{
                    dp[i][j] = dp[i-1][j] || dp[i][j-1];
                }
            }
        }
        return dp[p.length()][s.length()];
    }
}


Palindrome Partitioning II : ****

这个题,值得反复做。我是做了三遍,还是出错。这是一个题里用了俩个DP。

public class Solution {
    public int minCut(String s) {
        if(s == null)
            return 0;
        boolean[][] dp = new boolean[s.length()][s.length()];
        isP(s,dp);
        int[] res = new int[s.length()];
        
        for(int i=0; i
    
    
     
     =0; i--){
            for(int j=i; j
     
     
    
    


Interleaving String : ***

这个题dp处理的角度挺独特。递推关系一旦找对了入手方向是比较直观多。

public class Solution {
    public boolean isInterleave(String s1, String s2, String s3) {
        // Note: The Solution object is instantiated only once and is reused by each test case.
        if (s1.length() + s2.length() != s3.length()) return false;
        boolean[][] dp = new boolean[s1.length() + 1][s2.length() + 1];
        dp[0][0] = true;
        for(int i = 1; i
      
      


Post Office Prob **** *

这个题,最难的一类dp了。 需要抽象二维dp矩阵,需要构造辅助变量矩阵,需要遍历找最优过渡变量。

这个题,和stock IV 神似

On one line there are n houses. Give you an array of integer means the the position of each house. Now you need to pick k position to build k post office, so that the sum distance of each house to the nearest post office is the smallest. Return the least possible sum of all distances between each village and its nearest post office.

public class Solution {
    /**
     * @param A an integer array
     * @param k an integer
     * @return an integer
     */
    //initialize the dist matrix, dist[i][j] means the shortest sum of 
    //distance for all houses between i and j(inclusive) to get to the best
    //located one post office(which is the mid point).
    int[][] init(int []A)  
    {  
        int n = A.length;
        int [][]dis = new int [n+1][n+1];
        for(int i = 1; i <= n; i++) {  
            for(int j = i+1 ;j <= n;++j)  
            {  
                int ii = i, jj = j;
                while(ii < jj){
                    dis[i][j] += (A[jj-- -1]-A[ii++ -1]);
                }
            } 
        }
        return dis; 
    } 
    
    public int postOffice(int[] A, int k) {
        // Write your code here
        int n = A.length;
        Arrays.sort(A);
        int[][] dis = init(A);
        int[][] dp = new int[n + 1][k + 1];
        if(n == 0 || k >= A.length)
            return 0;
        int ans = Integer.MAX_VALUE;
        for(int i = 0;i <= n;++i)  {
            dp[i][1] = dis[1][i];
        }
        //The dp core part, which reminds me of stock problem IV
        //But here, I didn't figure out of the way to track local_min 
        //So, have to use a third loop to get min
        for(int p = 2; p <= k; p++) {
            for(int i = p+1; i <= n; i++) {
                dp[i][p] = Integer.MAX_VALUE;
                for(int j = p+1; j < i; j++) {  
                    if(dp[i][p] > dp[j][p-1] + dis[j+1][i])  
                        dp[i][p] = dp[j][p-1] + dis[j+1][i];   
                }  
            }
        }
        return dp[n][k];
    }
}


之前leetcode里很多比较难的dp都是string背景下的。 而我对于dp的接触和理解也是从leetcode开始。一定程度上导致了我比较熟练string背景的题目。

但是dp是有更广泛的应用的,尤其是二维dp的实用领域是很广泛的。 上一题post office就是经典。 以及 stock IV


下面俩道题是lintcode  backpack类的俩道题。 需要好好体会的是,这俩题为何放一起, m值到底是什么。


Minimum Adjustment Cost

Given an integer array, adjust each integers so that the difference of every adjacent integers are not greater than a given number target.

If the array before adjustment is A, the array after adjustment isB, you should minimize the sum of |A[i]-B[i]|

Example

Given [1,4,2,3] and target = 1, one of the solutions is [2,3,2,3], the adjustment cost is 2 and it's minimal.

Return 2.

Note

You can assume each number in the array is a positive integer and not greater than 100.


Backpack

Given n items with size Ai, an integer m denotes the size of a backpack. How full you can fill this backpack?


    public int MinAdjustmentCost(ArrayList
       
       
        
         A, int target) {
        // write your code here
        int n = A.size();
        int m = 100;
        int[][] dp = new int[n+1][m];
        for(int j=0; j < m; j++){
            dp[0][j] = Math.abs(A.get(0) - j-1);
        }
        for(int i=1; i
        
        
         
         = A[0])
                dp[0][j] = A[0];
        }
        for(int i=1; i
         
         
          
          = A[i]){
                    dp[i][j] = Math.max(dp[i][j], dp[i-1][j-A[i]] + A[i]);
                }
            }
        }
        return dp[n-1][m];
    }
         
         
        
        
       
       

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值