Skip to content

Latest commit

 

History

History
369 lines (288 loc) · 13.4 KB

search3-memorized-search-dynamic-programming.md

File metadata and controls

369 lines (288 loc) · 13.4 KB

//参考 http://hawstein.com/posts/dp-novice-to-advanced.html public class DynamicProgramming101 { /*动态规划(此处的Programming)是规划的意思

  • 1.什么是动态规划
  • 动态规划是一种解决问题的思想。
  • 比方说你有一个很大的问题。但是你可以把它分成无数--或者许多个子问题,
  • 模变小后的问题和原来的问题是同质的,除了规模变小,其它的都是一样的

*Example1:已知公交線路, 現從A到F地,求公交換乘方式 *比方说你A》B》C》D》E》F 换乘了 每一次其实等于就是一个小问题 *初始状态A求出先做B,然后到了B,在计算得出应该坐C,然后到了C得出应该坐D *最后求除了符合题目要求的步驟序列 * *

  • !!! 当前子问题的解将由上一次子问题的解推出,你找到了上一个子问题的解
  • !!!你就找到了本子问题的解。(想到loop和recursive了不是吗)
  • 动态规划算法通常基于一个递推公式及一个或多个初始状态。 然后一个个解决子问题,
  • 最终到子问题全部解决
  • Example2:如果我们有面值为1元、3元和5元的硬币若干枚,如何用|最少|的硬币凑够11元? *我用函数来表达吧比较清楚b(钱数)=硬币数
  • 最小的初始状态 :0元-0个硬币 b(0)=0
  • 下一个状态:1元-1个(1元)硬币 b(1)=1
  • b(2)=2,但是你的思维方法不能是 拿了2个1元硬币
  • 而应该是:我拿了一个一元硬币 这是1种拿法,然后剩下1元成为了b(1)=1;
  • 所以3元时候b(3)=1+b(2)=3 // 拿了1元后递推回b(2) 拿了一个3元的递推会b(0)
  • 当然 另一种方法是直接拿个一个3元的所以b(3)=min{d(3-1)+1, d(3-3)+1}
  • 所以。本题的解法就是
  •          vj表示本次取的硬币的面值
    
  • d(i)=min{ d(i-vj)+1 },其中i-vj >=0,
  • 上面讨论了2个非常简单的例子。现在让我们来看看对于更复杂的问题, 如何找到状态之间的
  • 转移方式(即找到状态转移方程)。为此我们要引入一个新词叫递推关系来将状态联系起来(说的还是状态转移方程)
  • Example3:
  • 一个序列有N个数:A[1],A[2],…,A[N],求出最长非降子序列的长度。 (讲DP基本都会讲到的一个问题LIS:longest increasing subsequence)
  • 正如上面我们讲的,面对这样一个问题,我们首先要定义一个“状态”来代表它的子问题, 并且找到它的解。注意,大部分情况下,
  • 某个状态只与它前面出现的状态有关, 而独立于后面的状态。
  • 假设数组是{5,3,4,8,6,7} //ps:最长升序子序列不一定要连续的,但是不能改变原数组的顺序,所以是3.4.6.7 *前1个数的LIS长度d(1)=1(序列:5) *前2个数的LIS长度d(2)=1(序列:3;3前面没有比3小的) *前3个数的LIS长度d(3)=2(序列:3,4;4前面有个比它小的3,所以d(3)=d(2)+1)
  •                                                              请注意这个max
    

*前4个数的LIS长度d(4)=3(序列:3,4,8;8前面比它小的有3个数,所以 d(4)=max{d(1),d(2),d(3)}+1=3)

  • 我觉得状态转移方程已经很明显了,如果我们已经求出了d(1)到d(i-1), 那么d(i)可以用下面的状态转移方程得到:
  • d(i) = max{1, d(j)+1},其中j<i且A[j]<=A[i]--->j不大于A[i]的最大的一个数
  • 用大白话解释就是,想要求d(i),就把i!!前面!!的各个子序列中, !!最后一个数不大于A[i]的序列长度加1!!,然后取出最大的长度即为d(i)。
  • 当然了,有可能i前面的各个子序列中最后一个数都大于A[i],那么d(i)=1, 即它自身成为一个长度为1的子序列。
  • 所以由此可得
  • d(5)=d(3)+1=2+1=3 //离6最近但是比6小的是4--d(3)
  • d(6)=d(4)+1=3=4
  • 这个方法是O(N2)

public int maxSubSequence(int person[]){ int[] d=new int[person.length]; int maxSubLen=1; for(int i=1;i<person.length;i++){ d[i]=1; for(int j=0;j<i;i++){ if(person[j]<=person[i]&&(d[j]+1)>d[i]){ d[i]=d[j]+1;} } if(d[i]>maxSubLen){maxSubLen=d[i]; } }

return maxSubLen; }

*/
//还有种nlogn的更巧妙解法 有兴趣可以google

}

https://www.1point3acres.com/bbs/thread-423857-1-1.html

The idea behind Recursive, Memorized DFS, and DP: https://leetcode.com/problems/reverse-pairs/discuss/97268/General-principles-behind-problems-similar-to-%22Reverse-Pairs%22

5. Longest Palindromic Substring

https://leetcode.com/problems/longest-palindromic-substring/

    // DP (2D array)
    /*
    
    https://leetcode.com/problems/longest-palindromic-substring/discuss/2921/Share-my-Java-solution-using-dynamic-programming
    
    
    subproblem:
    dp[i][j]    -   whether the substring [i, j] is palindromic string or not
    
    recurrence relation
    assume dp[0, i - 1][0, len] is optimized result
    dp[i][j] = true if char(i) == char(j) && dp[i + 1][j - 1] == true
    
    init:
    dp[0][0] = true // only one first char is a palindrome
    
    ans:
    longest (j - i + 1)
    
    because [i, j] depends on [i + 1, j - 1], so we should loop i from n - 1 to 0
    
    Runtime: 30 ms, faster than 47.95% of Java online submissions for Longest Palindromic Substring.
    Memory Usage: 38 MB, less than 54.30% of Java online submissions for Longest Palindromic Substring.
    */
    public String longestPalindrome(String s) {
        int n = s.length();
        if(n <= 1) return s;
        
        // subproblem
        boolean[][] dp = new boolean[n][n];
        
        // init
        dp[0][0] = true;
        
        // recurrence relation
        int start = 0, maxLen = 0;
        for (int i = n - 1; i >= 0; i--) {
            for (int j = i; j < n; j++) {
                dp[i][j] = s.charAt(i) == s.charAt(j) && (j - i <= 2 || dp[i + 1][j - 1]);
                if (dp[i][j] && maxLen < j - i + 1) {
                    start = i;
                    maxLen = j - i + 1;
                }
            }
        }
        
        return s.substring(start, start + maxLen);
    }
    // DP (1D array) (best DP solution)
    /*
    Optimization: substring() is costy. It's better to record start index and length, and do one substring finally.
    
    "abcba"
    j = 4
    - - - - T
    
    j = 3
    - - - T F
    
    j = 2
    _ _ T F F
    
    j = 1
    _ T F T F
    
    j = 0
    T F F F T   <-  the last "True" is the one with maximum length
    
    */
    public String longestPalindrome(String s) {
        int n = s.length();
        if(n <= 1) return s;
        
        // subproblem
        boolean[] dp = new boolean[n];
        
        // init
        dp[0] = true;
        
        // recurrence relation
        int start = 0, maxLen = 0;
        for (int i = n - 1; i >= 0; i--) {
            for (int j = n - 1; j >= i; j--) {
	    // for (int j = i; j < len; j++) {    // doesn't work, because the dp array records the [i + 1, j] palindomic information. If we scan j from i to len - 1, the information will be overwritten.
                dp[j] = s.charAt(i) == s.charAt(j) && (j - i < 3 || dp[j - 1]);
                if (dp[j] && maxLen < j - i + 1) {
                    start = i;
                    maxLen = j - i + 1;
                }
            }
        }
        
        return s.substring(start, start + maxLen);
    }

97. Interleaving String

https://leetcode.com/problems/interleaving-string/

    // 2.DP
    /*
    subproblem
    dp[i][j]  -   whether s1 [0, i], s2 [0, j] could interleaving and construct s3 [0, i + j + 2]
    
    recurrence relation:
    dp[i][j] = dp[i - 1][j] or dp[i][j - 1] if s3.charAt(i + j + 2) == s1.charAt(i) || s3.charAt(i + j + 1) == s2.charAt(j)
    
    */
    public boolean isInterleave(String s1, String s2, String s3) {
        if (s1 == null || s2 == null || s3 == null || s1.length() + s2.length() != s3.length()) return false;
        
        int l1 = s1.length(), l2 = s2.length(), l3 = s3.length();
        
        // subproblem
        boolean[][] dp = new boolean[l1 + 1][l2 + 1];
        
        // init
        dp[0][0] = true;
        for (int i = 1; i <= l1; i++) {
            dp[i][0] = dp[i - 1][0] && (s1.charAt(i - 1) == s3.charAt(i - 1));
        }
        for (int j = 1; j <= l2; j++) {
            dp[0][j] = dp[0][j - 1] && (s2.charAt(j - 1) == s3.charAt(j - 1));
        }
        
        // recurrence relation
        for (int i = 1; i <= l1; i++) {
            for (int j = 1; j <= l2; j++) {
                // ch == s1.charAt(i - 1) means curr char can mark from i+j-1 in s3, but the problem is if 
                // the previous char in s2 is equal to the previous char in s3? It's state[i-1][j] shows.
                if (s1.charAt(i - 1) == s3.charAt(i + j - 1)) {
                    dp[i][j] |= dp[i - 1][j];
                }
                // ch==s2.charAt(j-1) means curr char can mark from i+j-1 in s3, we need to make sure
                // the previous char in s1 is equal to the previous char in s3. state[i][j-1] shows this.
                if (s2.charAt(j - 1) == s3.charAt(i + j - 1)) {
                    dp[i][j] |= dp[i][j - 1];
                }
            }
        }
        
        return dp[l1][l2];
    }

486. Predict the Winner

https://leetcode.com/problems/predict-the-winner/

DFS -> Memorized DFS -> 2D DP -> 1D DP

class Solution {
    // 1. Recursive (without memorization)
    /*
    Time: O(2^N)
    Space: O(N)
    */
    // getScore method is used to get the max score diff of player 1 to player 2 (player 1 - player 2)
//     public boolean PredictTheWinner(int[] nums) {
//         return getScore(nums, 0, nums.length - 1) >= 0;
//     }
    
//     private int getScore(int[] nums, int i, int j) {
//         if (i == j) return nums[i];
//         return Math.max(nums[i] - getScore(nums, i + 1, j), nums[j] - getScore(nums, i, j - 1));
//     }
    
    // 2. Recursive (with memorization)
    /*
    Time: O(N^2) as we only calculate each [i, i] pair once
    Space: O(N^2)
    
    https://www.youtube.com/watch?v=g5wLHFTodm0
    */
//     public boolean PredictTheWinner(int[] nums) {
//         int[][] dp = new int[nums.length][nums.length];
//         getScore(nums, 0, nums.length - 1, dp);
//         return dp[0][nums.length - 1] >= 0;
//     }
    
//     private void getScore(int[] nums, int i, int j, int[][] dp) {
//         if (dp[i][j] != 0) return;
//         if (i == j) {
//             dp[i][j] = nums[i];
//             return;
//         }
//         getScore(nums, i + 1, j, dp);
//         getScore(nums, i, j - 1, dp);
//         dp[i][j] = Math.max(nums[i] - dp[i + 1][j], nums[j] - dp[i][j - 1]);
//     }
    
    // 3. DP
    /*
    https://leetcode.com/problems/predict-the-winner/discuss/96828/JAVA-9-lines-DP-solution-easy-to-understand-with-improvement-to-O(N)-space-complexity.
    Time: O(N^2)
    Space: O(N^2)
    */
//     public boolean PredictTheWinner(int[] nums) {
//         int n = nums.length;
//         int[][] dp = new int[n][n];
        
//         for (int i = 0; i < n; i++) {
//             dp[i][i] = nums[i];
//         }
        
//         for (int len = 1; len <= n - 1; len++) {
//             // range of [i, j], begin: [0, 0 + len], end: [n - len - 1, n - 1]
//             for (int i = 0; i < n - len; i++) {
//                 int j = i + len;
//                 dp[i][j] = Math.max(nums[i] - dp[i + 1][j], nums[j] - dp[i][j - 1]);
//             }
//         }
        
//         return dp[0][n - 1] >= 0;
//     }
    
    // Improvement: O(N) Space
    /*
    However, if you are interviewing with a good company, they may challenge you to further improve your code, probably in the aspect of space complexity. So far, we are using a n x n matrix so the space complexity is O(n^2). It actually can be improved to O(n). That can be done by changing our way of filling the table. We may use only one dimensional dp[i] and we start to fill the table at the bottom right corner where dp[4] = nums[4]. On the next step, we start to fill the second to the last line, where it starts from dp[3] = nums[3]. Then dp[4] = Math.max(nums[4] - dp[3], nums[3] - dp[4]). Then we fill the third to the last line where dp[2] = nums[2] and so on... Eventually after we fill the first line and after the filling, dp[4] will be the answer.

    */
    public boolean PredictTheWinner(int[] nums) {
        if (nums == null) { return true; }
        int n = nums.length;
        if ((n & 1) == 0) { return true; } // Improved with hot13399's comment.
        int[] dp = new int[n];
        for (int i = n - 1; i >= 0; i--) {
            for (int j = i; j < n; j++) {
                if (i == j) {
                    dp[i] = nums[i];
                } else {
                    dp[j] = Math.max(nums[i] - dp[j], nums[j] - dp[j - 1]);
                }
            }
        }
        return dp[n - 1] >= 0;
    }
    
}

315. Count of Smaller Numbers After Self

https://leetcode.com/problems/count-of-smaller-numbers-after-self/


493. Reverse Pairs

https://leetcode.com/problems/reverse-pairs/


Reference

  1. Pack 9 talks (背包九讲): https://github.com/tianyicui/pack