演算法刷題:LeetCode中常見的動態規劃題目

梅森上校發表於2018-03-04

動態規劃刷題筆記

53. Maximum Subarray

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

題目描述

Find the contiguous subarray within an array (containing at least one number) which has the largest sum.

For example, given the array [-2,1,-3,4,-1,2,1,-5,4],
the contiguous subarray [4,-1,2,1] has the largest sum = 6.

題目分析
把原字串分成很多不同的字串,然後求出字串中最大的。

把原字串分成很多不同的字串,通過max(f+A[i],A[i])就可以搞定,如果之前的對我沒貢獻,還不如另起一個字串
設狀態為 f[j],表示以 S[j] 結尾的最大連續子序列和,狀態轉移方程如下:
f=max(f+A[i],A[i]);//對於陣列裡的一個整數,它只有兩種 選擇:1、加入之前的 SubArray;2. 自己另起一個 SubArray。
maxsum=max(maxsum,f);// 求字串中最大的

演算法設計

public int maxSubArray(int[] A) {
        int n = A.length;
        int[] dp = new int[n];//dp[i] means the maximum subarray ending with A[i];
        dp[0] = A[0];
        int max = dp[0];

        for(int i = 1; i < n; i++){
            dp[i] = A[i] + (dp[i - 1] > 0 ? dp[i - 1] : 0);
            max = Math.max(max, dp[i]);
        }

        return max;
}

另外一種演算法設計:

public int maxSubArray(int[] A) {
    int max = A[0], dp = A[0];
    for (int i = 1; i < A.length; i++) {            
        dp = Math.max(dp + A[i] ,A[i]);
        max = Math.max(max, dp);
    }
    return max;
}

第三種演算法設計:

class Solution {
public:
    int maxSubArray(int A[], int n) {
        if(0==n) return 0;
        int f=0;//f[j],表示以 A[j] 結尾的最大連續子序列和
        int maxsum=A[0];
        for(int i=0;i<n;++i)
        {

            f=max(f+A[i],A[i]);//是否需要另起一個字串,如果之前的對我沒貢獻,還不如另起一個字串。
            maxsum=max(maxsum,f); //字串中最大的
        }
        return maxsum;
    }
};

85.Maximal Rectangle

題目連結:https://leetcode.com/problems/maximal-rectangle/description/

題目描述

Given a 2D binary matrix filled with 0’s and 1’s, find the largest rectangle containing only 1’s and return its area.

For example, given the following matrix:

1 0 1 0 0
1 0 1 1 1
1 1 1 1 1
1 0 0 1 0
Return 6.

演算法設計

public class Solution {
    public int maximalRectangle(char[][] matrix) {
        int area = 0, new_area, r, l;
        if(matrix.length > 0){
            int[] line = new int[matrix[0].length];
            boolean[] is_processed = new boolean[matrix[0].length];
            for(int i = 0; i < matrix.length; i++){
                for(int j = 0; j < matrix[i].length; j++){
                    if (matrix[i][j] == '1') {
                        line[j]++;
                        is_processed[j] = false;
                    } else {
                        line[j] = 0;
                        is_processed[j] = true;
                    }
                }
                for(int j = 0; j < matrix[i].length; j++){
                    if(is_processed[j]) continue;
                    r = l = 1;
                    while((j + r < line.length)&&(line[j + r] >= line[j])){
                        if(line[j + r] == line[j]) is_processed[j + r] = true;
                        r++;
                    }
                    while((j - l >= 0)&&(line[j - l] >= line[j])) l++;
                    new_area = (r + l - 1)*line[j];
                    if (new_area > area) area = new_area;
                }
            }
        } return area;
    }
}

97.Interleaving String

題目連結:https://leetcode.com/problems/interleaving-string/description/

題目描述

Given s1, s2, s3, find whether s3 is formed by the interleaving of s1 and s2.
For example,
Given:
s1 = “aabcc”,
s2 = “dbbca”,
When s3 = “aadbbcbcac”, return true.
When s3 = “aadbbbaccc”, return false.

題目分析

分析:判斷字串s3是否由s1,s2交叉存取組成

public boolean isInterleave(String s1, String s2, String s3) {

    if ((s1.length()+s2.length())!=s3.length()) return false;

    boolean[][] matrix = new boolean[s2.length()+1][s1.length()+1];

    matrix[0][0] = true;

    for (int i = 1; i < matrix[0].length; i++){
        matrix[0][i] = matrix[0][i-1]&&(s1.charAt(i-1)==s3.charAt(i-1));
    }

    for (int i = 1; i < matrix.length; i++){
        matrix[i][0] = matrix[i-1][0]&&(s2.charAt(i-1)==s3.charAt(i-1));
    }

    for (int i = 1; i < matrix.length; i++){
        for (int j = 1; j < matrix[0].length; j++){
            matrix[i][j] = (matrix[i-1][j]&&(s2.charAt(i-1)==s3.charAt(i+j-1)))
                    || (matrix[i][j-1]&&(s1.charAt(j-1)==s3.charAt(i+j-1)));
        }
    }

    return matrix[s2.length()][s1.length()];

}

DFS解法

To solve this problem, let’s look at if s1[0 ~ i] s2[0 ~ j] can be interleaved to s3[0 ~ k].

Start from indices0, 0, 0 and compare s1[i] == s3[k] or s2[j] == s3[k]
Return valid only if either i or j match k and the remaining is also valid
Caching is the key to performance. This is very similar to top down dp
Only need to cache invalid[i][j] since most of the case s1[0 ~ i] and s2[0 ~ j] does not form s3[0 ~ k]. Also tested caching valid[i][j] the run time is also 1ms
Many guys use substring but it’s duplicate code since substring itself is checking char by char. We are already doing so

public boolean isInterleave(String s1, String s2, String s3) {
    char[] c1 = s1.toCharArray(), c2 = s2.toCharArray(), c3 = s3.toCharArray();
    int m = s1.length(), n = s2.length();
    if(m + n != s3.length()) return false;
    return dfs(c1, c2, c3, 0, 0, 0, new boolean[m + 1][n + 1]);
}

public boolean dfs(char[] c1, char[] c2, char[] c3, int i, int j, int k, boolean[][] invalid) {
    if(invalid[i][j]) return false;
    if(k == c3.length) return true;
    boolean valid = 
        i < c1.length && c1[i] == c3[k] && dfs(c1, c2, c3, i + 1, j, k + 1, invalid) || 
        j < c2.length && c2[j] == c3[k] && dfs(c1, c2, c3, i, j + 1, k + 1, invalid);
    if(!valid) invalid[i][j] = true;
    return valid;
}

120. Triangle

原題連結地址:https://leetcode.com/problems/triangle/description/

題目描述

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.

題目分析

設狀態為 f (i; j ),表示從位置 (i; j ) 出發,路徑的最小和,
則狀態轉移方程為:f(i,j)=min{f(i+1,j),f(i+1,j+1)}+(i,j)

演算法設計

//演算法設計第一種
int minimumTotal(vector<vector<int> > &triangle) {
    int n = triangle.size();
    vector<int> minlen(triangle.back());
    for (int layer = n-2; layer >= 0; layer--) // For each layer
    {
        for (int i = 0; i <= layer; i++) // Check its every 'node'
        {
            // Find the lesser of its two children, and sum the current value in the triangle with it.
            minlen[i] = min(minlen[i], minlen[i+1]) + triangle[layer][i]; 
        }
    }
    return minlen[0];
}

演算法設計第二種:

public int minimumTotal(List<List<Integer>> triangle) {
        if(triangle.size() == 0)
            return 0;

        for (int i=triangle.size() - 2; i>=0; i--) {
            for (int j=0; j<=i; j++) {
                List<Integer> nextRow = triangle.get(i+1);
                int sum = Math.min(nextRow.get(j), nextRow.get(j+1)) + triangle.get(i).get(j);
                triangle.get(i).set(j, sum);
            }
        }
        return triangle.get(0).get(0);
    }

132. Palindrome Partitioning II

題目描述

Given a string s, partition s such that every substring of the partition is a palindrome.
Return the minimum cuts needed for a palindrome partitioning of s.
For example, given s = “aab”,
Return 1 since the palindrome partitioning [“aa”,”b”] could be produced using 1 cut.

題目分析

題意分析: 對輸入的字串劃分為一組迴文字串,最小的分割次數
定義狀態 f(i,j) 表示區間 [i,j] 之間最小的 cut 數,則狀態轉移方程為
這是一個二維函式,實際寫程式碼比較麻煩。
所以要轉換成一維 DP。如果每次,從 i 往右掃描,每找到一個迴文就算一次 DP 的話,就可以
轉換為 f(i)= 區間 [i, n-1] 之間最小的 cut 數,n 為字串長度,則狀態轉移方程為

一個問題出現了,就是如何判斷 [i,j] 是否是迴文?每次都從 i 到 j 比較一遍?太浪費了,這 裡也是一個 DP 問題。
定義狀態 P[i][j] = true if [i,j] 為迴文,那麼
P[i][j] = str[i] == str[j] && P[i+1][j-1]

演算法設計

演算法設計第一種

public int minCut(String s) {
        // validate input
        if (s == null || s.length() <= 1) {
            return 0;
        }
        // dp
        int N = s.length();
        int[] dp = IntStream.range(0, N).toArray(); // initial value: dp[i] = i

        for (int mid = 1; mid <  N; mid++) { // iterate through all chars as mid point of palindrome
            // CASE 1. odd len: center is at index mid, expand on both sides
            for (int start = mid, end = mid; start >= 0 && end < N && s.charAt(start) == s.charAt(end); start--, end++) {
                int newCutAtEnd = (start == 0) ? 0 : dp[start - 1] + 1;
                dp[end] = Math.min(dp[end], newCutAtEnd);
            }
            // CASE 2: even len: center is between [mid-1,mid], expand on both sides
            for (int start = mid - 1, end = mid; start >= 0 && end < N && s.charAt(start) == s.charAt(end); start--, end++) {
                int newCutAtEnd = (start == 0) ? 0 : dp[start - 1] + 1;
                dp[end] = Math.min(dp[end], newCutAtEnd);
            }
        }
        return dp[N - 1];
    }

演算法設計第二種

class Solution {
public:
  int minCut(string s)
 {
    const int n = s.size();
    int f[n+1];
    bool p[n][n];
    fill_n(&p[0][0], n * n, false);
//the worst case is cutting by each char
    for (int i = 0; i <= n; i++)
            f[i] = n - 1 - i; // 最後一個 f[n]=-1
    for (int i = n - 1; i >= 0; i--)
    {
        for (int j = i; j < n; j++)
         {
            if (s[i] == s[j] && (j - i < 2 || p[i + 1][j - 1])) 
            { 
                    p[i][j] = true;
                    f[i] = min(f[i], f[j + 1] + 1);
           }
        }
    }
    return f[0];
}
}

演算法設計第三種

This can be solved by two points:

cut[i] is the minimum of cut[j - 1] + 1 (j <= i), if [j, i] is palindrome.
If [j, i] is palindrome, [j + 1, i - 1] is palindrome, and c[j] == c[i].
The 2nd point reminds us of using dp (caching).

a b a | c c
j i
j-1 | [j, i] is palindrome
cut(j-1) + 1

public int minCut(String s) {
    char[] c = s.toCharArray();
    int n = c.length;
    int[] cut = new int[n];
    boolean[][] pal = new boolean[n][n];

    for(int i = 0; i < n; i++) {
        int min = i;
        for(int j = 0; j <= i; j++) {
            if(c[j] == c[i] && (j + 1 > i - 1 || pal[j + 1][i - 1])) {
                pal[j][i] = true;  
                min = j == 0 ? 0 : Math.min(min, cut[j - 1] + 1);
            }
        }
        cut[i] = min;
    }
    return cut[n - 1];
}

123.Best Time to Buy and Sell Stock III

題目連結:https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/description/

題目描述

Say you have an array for which the ith element is the price of a given stock on day i.
Design an algorithm to find the maximum profit. You may complete at most two transactions.

Note:
You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).

題目分析

設狀態f(i)表示區間[0,i-1]上的最大利潤,設定狀態g(i),表示區間[i,n-1]上最大利潤。
則最大利潤為max{f(i)+g(i)};允許在一天內買進又賣出,相當於不交易,因為題目的規定是最多兩次,而不是一定要兩次。

演算法設計

class Solution {
public:
int maxProfit(vector<int>& prices)
 {
    if (prices.size() < 2) return 0;
    const int n = prices.size();
    vector<int> f(n, 0);
    vector<int> g(n, 0);
    for (int i = 1, valley = prices[0]; i < n; ++i) {
        valley = min(valley, prices[i]);
        f[i] = max(f[i - 1], prices[i] - valley);
    }
    for (int i = n - 2, peak = prices[n - 1]; i >= 0; --i) {
        peak = max(peak, prices[i]);
        g[i] = max(g[i], peak - prices[i]);
    }
    int max_profit = 0;
    for (int i = 0; i < n; ++i)
        max_profit = max(max_profit, f[i] + g[i]);
    return max_profit;
    }
};

JAVA程式碼演算法設計

public int maxProfit(int[] prices) {
    // these four variables represent your profit after executing corresponding transaction
    // in the beginning, your profit is 0. 
    // when you buy a stock ,the profit will be deducted of the price of stock.
    int firstBuy = Integer.MIN_VALUE, firstSell = 0;
    int secondBuy = Integer.MIN_VALUE, secondSell = 0;

    for (int curPrice : prices) {
        if (firstBuy < -curPrice) firstBuy = -curPrice; // the max profit after you buy first stock
        if (firstSell < firstBuy + curPrice) firstSell = firstBuy + curPrice; // the max profit after you sell it
        if (secondBuy < firstSell - curPrice) secondBuy = firstSell - curPrice; // the max profit after you buy the second stock
        if (secondSell < secondBuy + curPrice) secondSell = secondBuy + curPrice; // the max profit after you sell the second stock
    }

    return secondSell; // secondSell will be the max profit after passing the prices
}

87.Scramble String

題目連結:https://leetcode.com/problems/scramble-string/description/

64.Minimum Path Sum

題目連結:https://leetcode.com/problems/minimum-path-sum/description/

72.Edit Distance

題目連結:https://leetcode.com/problems/edit-distance/description/

91.Decode Ways

題目連結:https://leetcode.com/problems/decode-ways/description/

639.Decode Ways II

題目連結:https://leetcode.com/problems/decode-ways-ii/description/

115.Distinct Subsequences(不同的子序列)

題目連結:https://leetcode.com/problems/distinct-subsequences/description/

139.Word Break

題目連結:https://leetcode.com/problems/word-break/description/

題目描述

Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, determine if s can be segmented into a space-separated sequence of one or more dictionary words. You may assume the dictionary does not contain duplicate words.

For example, given
s = “leetcode”,
dict = [“leet”, “code”].

Return true because “leetcode” can be segmented as “leet code”.

UPDATE (2017/1/4):
The wordDict parameter had been changed to a list of strings (instead of a set of strings). Please reload the code definition to get the latest changes.

演算法設計

public class Solution {
    public class TrieNode {
        boolean isWord;
        TrieNode[] c;

        public TrieNode(){
            isWord = false;
            c = new TrieNode[128];
        }
    }

    public void addWord(TrieNode t, String w) {
        for (int i = 0; i < w.length(); i++) {
            int j = (int)w.charAt(i); 
            if (t.c[j] == null) t.c[j] = new TrieNode();
            t = t.c[j];
        }
        t.isWord = true;
    }

    public boolean wordBreak(String s, Set<String> wordDict) {
        TrieNode t = new TrieNode(), cur;
        for (String i : wordDict) addWord(t, i);
        char[] str = s.toCharArray();
        int len = str.length;
        boolean[] f = new boolean[len + 1];
        f[len] = true;

        for (int i = len - 1; i >= 0; i--) {
            //System.out.println(str[i]);
            cur = t;
            for (int j = i; cur != null && j < len ; j++) {
                cur = cur.c[(int)str[j]];
                if (cur != null && cur.isWord && f[j + 1]) {
                    f[i] = true;
                    break;
                }
            }
        }
        return f[0];
    }
}

不同的解法

public class Solution {
    public boolean wordBreak(String s, Set<String> dict) {

        boolean[] f = new boolean[s.length() + 1];

        f[0] = true;


        /* First DP
        for(int i = 1; i <= s.length(); i++){
            for(String str: dict){
                if(str.length() <= i){
                    if(f[i - str.length()]){
                        if(s.substring(i-str.length(), i).equals(str)){
                            f[i] = true;
                            break;
                        }
                    }
                }
            }
        }*/

        //Second DP
        for(int i=1; i <= s.length(); i++){
            for(int j=0; j < i; j++){
                if(f[j] && dict.contains(s.substring(j, i))){
                    f[i] = true;
                    break;
                }
            }
        }

        return f[s.length()];
    }
}

140.Word Break II

題目連結:https://leetcode.com/problems/word-break-ii/description/

題目描述

Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, add spaces in s to construct a sentence where each word is a valid dictionary word. You may assume the dictionary does not contain duplicate words.

Return all such possible sentences.

For example, given
s = “catsanddog”,
dict = [“cat”, “cats”, “and”, “sand”, “dog”].

A solution is [“cats and dog”, “cat sand dog”].

UPDATE (2017/1/4):
The wordDict parameter had been changed to a list of strings (instead of a set of strings). Please reload the code definition to get the latest changes.

演算法設計

public class Solution {
    HashMap<Integer, List<String>> dp = new HashMap<>();

    public List<String> wordBreak(String s, Set<String> wordDict) {
        int maxLength = -1;
        for(String ss : wordDict) maxLength = Math.max(maxLength, ss.length());
        return addSpaces(s, wordDict, 0, maxLength);
    }

    private List<String> addSpaces(String s, Set<String> wordDict, int start, int max){
        List<String> words = new ArrayList<>();
        if(start == s.length()) {
            words.add("");
            return words;
        }
        for(int i = start + 1; i <= max + start && i <= s.length(); i++){
            String temp = s.substring(start, i);
            if(wordDict.contains(temp)){
                List<String> ll;
                if(dp.containsKey(i)) ll = dp.get(i);
                else ll = addSpaces(s, wordDict, i, max);
                for(String ss : ll) words.add(temp + (ss.equals("") ? "" : " ") + ss);
            }

        }
        dp.put(start, words);
        return words;
    }
}

(未完待續)

相關文章