1. 程式人生 > 實用技巧 >LeetCode 動態規劃

LeetCode 動態規劃

基礎部分

70. 爬樓梯

簡單

假設你正在爬樓梯。需要 n 階你才能到達樓頂。

每次你可以爬 1 或 2 個臺階。你有多少種不同的方法可以爬到樓頂呢?

注意:給定 n 是一個正整數。

示例 1:

輸入: 2
輸出: 2
解釋: 有兩種方法可以爬到樓頂。
1.  1 階 + 1 階
2.  2 階

示例 2:

輸入: 3
輸出: 3
解釋: 有三種方法可以爬到樓頂。
1.  1 階 + 1 階 + 1 階
2.  1 階 + 2 階
3.  2 階 + 1 階
class Solution {
    public int climbStairs(int n) {
        if (n < 4)  return n;
        int[] res = new int[n];
        res[0] = 1;
        res[1] = 2;
        for (int i = 2; i < n; i++)
            res[i] = res[i-1] + res[i-2];
        return res[n-1];
    }
}

198. 打家劫舍

簡單

你是一個專業的小偷,計劃偷竊沿街的房屋。每間房內都藏有一定的現金,影響你偷竊的唯一制約因素就是相鄰的房屋裝有相互連通的防盜系統,如果兩間相鄰的房屋在同一晚上被小偷闖入,系統會自動報警

給定一個代表每個房屋存放金額的非負整數陣列,計算你 不觸動警報裝置的情況下 ,一夜之內能夠偷竊到的最高金額。

示例 1:

輸入:[1,2,3,1]
輸出:4
解釋:偷竊 1 號房屋 (金額 = 1) ,然後偷竊 3 號房屋 (金額 = 3)。
     偷竊到的最高金額 = 1 + 3 = 4 。

示例 2:

輸入:[2,7,9,3,1]
輸出:12
解釋:偷竊 1 號房屋 (金額 = 2), 偷竊 3 號房屋 (金額 = 9),接著偷竊 5 號房屋 (金額 = 1)。
     偷竊到的最高金額 = 2 + 9 + 1 = 12 。

提示:

  • 0 <= nums.length <= 100
  • 0 <= nums[i] <= 400
class Solution {
    public int rob(int[] nums) {
        int len = nums.length;
        if (len == 0) return 0;
        if (len == 1) return nums[0];
        int[] arr = new int[len];
        arr[0] = nums[0];
        arr[1] = Math.max(arr[0],nums[1]);
        for (int i = 2; i < len; i++) {
            if (arr[i-2] + nums[i] > arr[i-1]){
                arr[i] = arr[i-2] + nums[i];
            }else {
                arr[i] = arr[i-1];
            }
        }
        return Math.max(arr[len-1],arr[len-2]);
    }
}

213. 打家劫舍 II

中等

你是一個專業的小偷,計劃偷竊沿街的房屋,每間房內都藏有一定的現金。這個地方所有的房屋都圍成一圈,這意味著第一個房屋和最後一個房屋是緊挨著的。同時,相鄰的房屋裝有相互連通的防盜系統,如果兩間相鄰的房屋在同一晚上被小偷闖入,系統會自動報警

給定一個代表每個房屋存放金額的非負整數陣列,計算你在不觸動警報裝置的情況下,能夠偷竊到的最高金額。

示例 1:

輸入: [2,3,2]
輸出: 3
解釋: 你不能先偷竊 1 號房屋(金額 = 2),然後偷竊 3 號房屋(金額 = 2), 因為他們是相鄰的。

示例 2:

輸入: [1,2,3,1]
輸出: 4
解釋: 你可以先偷竊 1 號房屋(金額 = 1),然後偷竊 3 號房屋(金額 = 3)。
     偷竊到的最高金額 = 1 + 3 = 4 。
class Solution { //正反各偷一遍,取最大值
    public int rob(int[] nums) {
        int len = nums.length;
        if (len == 0) return 0;
        if (len == 1) return nums[0];
        if (len == 2) return Math.max(nums[0],nums[1]);
        int a = helper(nums);
        int tmp;
        for (int i = 0; i < len/2; i++) {
            tmp = nums[i];
            nums[i] = nums[len-i-1];
            nums[len-i-1] = tmp;
        }
        int b = helper(nums);
        return a > b ? a : b;
    }
    public int helper(int[] nums) {
        int len = nums.length;
        int[] arr = new int[len];
        boolean[] robed = new boolean[len];
        arr[0] = nums[0];
        robed[0] = true;
        if (nums[0] > nums[1]){
            arr[1] = nums[0];
        }else {
            arr[1] = nums[1];
            robed[0] = false;
            robed[1] = true;
        }
        for (int i = 2; i < len-1; i++) {
            if (arr[i-2] + nums[i] > arr[i-1]){
                robed[i-2] = true;
                robed[i-1] = false;
                robed[i] = true;
                arr[i] = arr[i-2] + nums[i];
            }else {
                arr[i] = arr[i-1];
            }
        }
        int ans = Math.max(arr[len-2],arr[len-3]);
        if (!robed[0] && !robed[len-2])
            ans += nums[len-1];
        return ans;
    }
}

64. 最小路徑和

中等

給定一個包含非負整數的 m x n 網格,請找出一條從左上角到右下角的路徑,使得路徑上的數字總和為最小。

說明:每次只能向下或者向右移動一步。

示例:

輸入:
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
輸出: 7
解釋: 因為路徑 1→3→1→1→1 的總和最小。
class Solution {
    public int minPathSum(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        int[][] dp = new int[m][n];
        dp[0][0] = grid[0][0];
        for (int i = 1; i < m; i++)
            dp[i][0] = dp[i-1][0] + grid[i][0];
        for (int i = 1; i < n; i++)
            dp[0][i] = dp[0][i-1] + grid[0][i];
        for (int i = 1; i < m; i++)
            for (int j = 1; j < n; j++)
                dp[i][j] = grid[i][j] + Math.min(dp[i-1][j],dp[i][j-1]);
        return dp[m-1][n-1];
    }
}

62. 不同路徑

中等

一個機器人位於一個 m x n 網格的左上角 (起始點在下圖中標記為“Start” )。

機器人每次只能向下或者向右移動一步。機器人試圖達到網格的右下角(在下圖中標記為“Finish”)。

問總共有多少條不同的路徑?

例如,上圖是一個7 x 3 的網格。有多少可能的路徑?

示例 1:

輸入: m = 3, n = 2
輸出: 3
解釋:
從左上角開始,總共有 3 條路徑可以到達右下角。
1. 向右 -> 向右 -> 向下
2. 向右 -> 向下 -> 向右
3. 向下 -> 向右 -> 向右

示例 2:

輸入: m = 7, n = 3
輸出: 28

提示:

  • 1 <= m, n <= 100
  • 題目資料保證答案小於等於 2 * 10 ^ 9
class Solution {
    public int uniquePaths(int m, int n) {
        int[][] dp = new int[m][n];
        for (int i = 0; i < m; i++) dp[i][0] = 1;
        for (int i = 1; i < n; i++) dp[0][i] = 1;
        for (int i = 1; i < m; i++)
            for (int j = 1; j < n; j++)
                dp[i][j] = dp[i-1][j]+ dp[i][j-1];
        return dp[m-1][n-1];
    }
}

303. 區域和檢索 - 陣列不可變

簡單

給定一個整數陣列 nums,求出陣列從索引 ij (ij) 範圍內元素的總和,包含 i, j 兩點。

示例:

給定 nums = [-2, 0, 3, -5, 2, -1],求和函式為 sumRange()

sumRange(0, 2) -> 1
sumRange(2, 5) -> -1
sumRange(0, 5) -> -3

說明:

  1. 你可以假設陣列不可變。
  2. 會多次呼叫 sumRange 方法。
class NumArray {
    int len;
    int[] nums;

    public NumArray(int[] nums) {
        this.nums = nums;
        len = nums.length;
    }

    public int sumRange(int i, int j) {
        int[] dp = new int[j-i+1];
        dp[0] = nums[i];
        for (int index = 1; index < dp.length; index++)
            dp[index] += dp[index-1] + nums[index+i];
        return dp[dp.length-1];
    }
}

413. 等差數列劃分

中等

如果一個數列至少有三個元素,並且任意兩個相鄰元素之差相同,則稱該數列為等差數列。

例如,以下數列為等差數列:

1, 3, 5, 7, 9
7, 7, 7, 7
3, -1, -5, -9

以下數列不是等差數列。

1, 1, 2, 5, 7

陣列 A 包含 N 個數,且索引從0開始。陣列 A 的一個子陣列劃分為陣列 (P, Q),P 與 Q 是整數且滿足 0<=P<Q<N 。

如果滿足以下條件,則稱子陣列(P, Q)為等差陣列:

元素 A[P], A[p + 1], ..., A[Q - 1], A[Q] 是等差的。並且 P + 1 < Q 。

函式要返回陣列 A 中所有為等差陣列的子陣列個數。

示例:

A = [1, 2, 3, 4]

返回: 3, A 中有三個子等差陣列: [1, 2, 3], [2, 3, 4] 以及自身 [1, 2, 3, 4]。
class Solution { //子陣列的元素是連續的,那也太簡單了
    public int numberOfArithmeticSlices(int[] A) {
        int len = A.length;
        int[] dp = new int[len];
        int res = 0;
        for (int i = 2; i < len; i++) {
            if (A[i]+A[i-2] == A[i-1]+A[i-1]){
                dp[i] = dp[i-1] + 1;
                res += dp[i];
            }
        }
        return res;
    }
}

343. 整數拆分

中等

給定一個正整數 n,將其拆分為至少兩個正整數的和,並使這些整數的乘積最大化。 返回你可以獲得的最大乘積。

示例 1:

輸入: 2
輸出: 1
解釋: 2 = 1 + 1, 1 × 1 = 1。

示例 2:

輸入: 10
輸出: 36
解釋: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。

說明: 你可以假設 n 不小於 2 且不大於 58。

class Solution { //強行dp
    public int integerBreak(int n) {
        int[] dp = new int[n+5];
        dp[2] = 1;
        dp[3] = 2;
        dp[4] = 4;
        dp[5] = 6;
        dp[6] = 9;
        for (int i = 7; i < n+1; i++) {
            dp[i] = dp[i-3] * 3;
        }
        return dp[n];
    }
}

279. 完全平方數

中等

給定正整數 n,找到若干個完全平方數(比如 1, 4, 9, 16, ...)使得它們的和等於 n。你需要讓組成和的完全平方數的個數最少。

示例 1:

輸入: n = 12
輸出: 3 
解釋: 12 = 4 + 4 + 4.

示例 2:

輸入: n = 13
輸出: 2
解釋: 13 = 4 + 9.
class Solution { //麻煩了,垃圾
    public int numSquares(int n) {
        int[] dp = new int[n+1];
        for (int i = 1; i < n + 1; i++) {
            int a = (int) Math.sqrt(i);
            if (a*a == i) {
                dp[i] = 1;
            } else {
                int fill = Integer.MAX_VALUE;
                for (int l = 1,r = i-1;l <= r;l++,r--){ //雙指標找最小
                    if (dp[l]+dp[r] < fill){
                        fill = dp[l]+dp[r];
                        if (fill == 2) break;
                    }
                }
                dp[i] = fill;
            }
        }
        return dp[n];
    }
}
class Solution {
    public int numSquares(int n) {
        int[] dp = new int[n + 1]; // 預設初始化值都為0
        for (int i = 1; i <= n; i++) {
            dp[i] = i; // 最壞的情況就是每次+1
            for (int j = 1; i - j * j >= 0; j++) {
                dp[i] = Math.min(dp[i], dp[i - j * j] + 1); // 動態轉移方程
            }
        }
        return dp[n];
    }
}

91. 解碼方法

中等

一條包含字母 A-Z 的訊息通過以下方式進行了編碼:

'A' -> 1
'B' -> 2
...
'Z' -> 26

給定一個只包含數字的非空字串,請計算解碼方法的總數。

示例 1:

輸入: "12"
輸出: 2
解釋: 它可以解碼為 "AB"(1 2)或者 "L"(12)。

示例 2:

輸入: "226"
輸出: 3
解釋: 它可以解碼為 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。
class Solution {
    public int numDecodings(String s) {
        char[] chars = s.toCharArray();
        int len = chars.length;
        if (len == 0 || s.charAt(0)=='0') return 0;
        int[] nums = new int[len];
        for (int i = 0; i < len; i++) nums[i] = chars[i] - '0';
        for (int i = 1; i < len; i++)
            if (nums[i] == 0 && (nums[i-1] == 0 || nums[i-1] > 2)) return 0;
        int[] dp = new int[len+1];
        dp[0] = 1;
        int n1 = 0, n2 = 1, n3 = 1;
        for (int i = 1; i < len; i++) {
            int num = nums[i-1]*10+nums[i];
            if (nums[i] == 0){
                dp[i-1] = 1;
                dp[i] = 1;
                dp[i+1] = 1;
                i++;
                n1 = 0;
                n2 = 1;
                n3 = 1;
            }else if (num > 0 && num < 27){
                n1 = n2;
                n2 = n3;
                n3 = n1 + n2;
                dp[i] = n3;
            }else {
                n1 = 0;
                n2 = 1;
                n3 = 1;
                dp[i] = 1;
            }
        }
        int res = 1;
        for (int i = 1; i < len; i++)
            if (dp[i] >= dp[i+1]) res *= dp[i];
        return res;
    }
}

300. 最長上升子序列

中等

給定一個無序的整數陣列,找到其中最長上升子序列的長度。

示例:

輸入: [10,9,2,5,3,7,101,18]
輸出: 4 
解釋: 最長的上升子序列是 [2,3,7,101],它的長度是 4。

說明:

  • 可能會有多種最長上升子序列的組合,你只需要輸出對應的長度即可。
  • 你演算法的時間複雜度應該為 O(n2) 。

進階: 你能將演算法的時間複雜度降低到 O(n log n) 嗎?

class Solution { //O(n^2)
    public int lengthOfLIS(int[] nums) {
        int len = nums.length;
        if (len < 2) return len;
        int[] dp = new int[len];
        int res = 1;
        dp[0] = 1;
        for (int i = 1; i < len; i++) {
            int fill = 1;
            for (int j = 0; j < i; j++) {
                if (nums[j] < nums[i])
                    fill = Math.max(fill,dp[j]+1);
            }
            dp[i] = fill;
            res = Math.max(res,fill);
        }
        return res;
    }
}

646. 最長數對鏈

中等

給出 n 個數對。 在每一個數對中,第一個數字總是比第二個數字小。

現在,我們定義一種跟隨關係,當且僅當 b < c 時,數對(c, d) 才可以跟在 (a, b) 後面。我們用這種形式來構造一個數對鏈。

給定一個對數集合,找出能夠形成的最長數對鏈的長度。你不需要用到所有的數對,你可以以任何順序選擇其中的一些數對來構造。

示例 :

輸入: [[1,2], [2,3], [3,4]]
輸出: 2
解釋: 最長的數對鏈是 [1,2] -> [3,4]

注意:

  1. 給出數對的個數在 [1, 1000] 範圍內。
class Solution {
    public int findLongestChain(int[][] pairs) {
        Arrays.sort(pairs,(a,b)->(a[0]-b[0]));
        int[] dp = new int[pairs.length];
        dp[0] = 1;
        int res = 1;
        for (int i = 1; i < dp.length; i++) {
            int fill = 1;
            for (int j = 0; j < i; j++) {
                if (pairs[j][1] < pairs[i][0]){
                    fill = Math.max(fill,dp[j]+1);
                }
            }
            dp[i] = fill;
            if (fill > res) res = fill;
        }
        return res;
    }
}

376. 擺動序列

中等

如果連續數字之間的差嚴格地在正數和負數之間交替,則數字序列稱為擺動序列。第一個差(如果存在的話)可能是正數或負數。少於兩個元素的序列也是擺動序列。

例如, [1,7,4,9,2,5] 是一個擺動序列,因為差值 (6,-3,5,-7,3) 是正負交替出現的。相反, [1,4,7,2,5][1,7,4,5,5] 不是擺動序列,第一個序列是因為它的前兩個差值都是正數,第二個序列是因為它的最後一個差值為零。

給定一個整數序列,返回作為擺動序列的最長子序列的長度。 通過從原始序列中刪除一些(也可以不刪除)元素來獲得子序列,剩下的元素保持其原始順序。

示例 1:

輸入: [1,7,4,9,2,5]
輸出: 6 
解釋: 整個序列均為擺動序列。

示例 2:

輸入: [1,17,5,10,13,15,10,5,16,8]
輸出: 7
解釋: 這個序列包含幾個長度為 7 擺動序列,其中一個可為[1,17,10,13,10,16,8]。

示例 3:

輸入: [1,2,3,4,5,6,7,8,9]
輸出: 2

進階:
你能否用 O(n) 時間複雜度完成此題?

class Solution { //真丶O(n)
    public int wiggleMaxLength(int[] nums) {
        
        if (nums.length < 2) return nums.length;

        //去掉前面重複的數字
        int cur = 0;
        while (nums[cur] == nums[cur+1] && cur < nums.length-2) cur++;
        int[] arr = new int[nums.length-cur];
        int len = arr.length;
        System.arraycopy(nums, cur, arr, 0, len);
        
        if (len < 2) return len;
        
        int[] dp = new int[len];
        int max = 0, min = 0; //存峰/谷的index
        dp[0] = 1;
        boolean toFindBig = arr[1] > arr[0];
        for (int i = 1; i < len; i++) {
            if (toFindBig){ //找更大的
                if (arr[i] > arr[min]){ //比谷底大,找到了更大的
                    dp[i] = dp[min] + 1;
                    max = i; //更新峰頂
                    toFindBig = false; //找完大的,該找小的了
                }else {
                    dp[i] = dp[min]; //沒找到,谷底降低
                    min = i;
                }
            }else { //找更小的
                if (arr[i] < arr[max]){
                    dp[i] = dp[max] + 1;
                    toFindBig = true;
                    min = i;
                }else {
                    dp[i] = dp[max];
                    max = i;
                }
            }
        }
        return dp[len-1];
    }
}

1143. 最長公共子序列

中等

給定兩個字串 text1text2,返回這兩個字串的最長公共子序列的長度。

一個字串的 子序列 是指這樣一個新的字串:它是由原字串在不改變字元的相對順序的情況下刪除某些字元(也可以不刪除任何字元)後組成的新字串。
例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。兩個字串的「公共子序列」是這兩個字串所共同擁有的子序列。

若這兩個字串沒有公共子序列,則返回 0。

示例 1:

輸入:text1 = "abcde", text2 = "ace" 
輸出:3  
解釋:最長公共子序列是 "ace",它的長度為 3。

示例 2:

輸入:text1 = "abc", text2 = "abc"
輸出:3
解釋:最長公共子序列是 "abc",它的長度為 3。

示例 3:

輸入:text1 = "abc", text2 = "def"
輸出:0
解釋:兩個字串沒有公共子序列,返回 0。

提示:

  • 1 <= text1.length <= 1000
  • 1 <= text2.length <= 1000
  • 輸入的字串只含有小寫英文字元。
class Solution { //遞迴,超時了
    public int longestCommonSubsequence(String text1, String text2) {
        int l1 = text1.length();
        int l2 = text2.length();
        if (l1 == 0 || l2 == 0) return 0;
        if (text1.charAt(l1-1) == text2.charAt(l2-1)){
            return longestCommonSubsequence(text1.substring(0,l1-1), text2.substring(0,l2-1)) + 1;
        }else {
            int a = longestCommonSubsequence(text1, text2.substring(0,l2-1));
            int b = longestCommonSubsequence(text1.substring(0,l1-1), text2);
            return a > b ? a : b;
        }
    }
}
class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        char[] chars1 = text1.toCharArray();
        char[] chars2 = text2.toCharArray();
        int l1 = chars1.length;
        int l2 = chars2.length;
        int[][] dp = new int[l1+1][l2+1]; //加半圈0,省著處理越界,很麻煩
        for (int i = 1; i < l1+1; i++) {
            for (int j = 1; j < l2+1; j++) {
                if (chars1[i-1] == chars2[j-1]){
                    dp[i][j] = dp[i-1][j-1] + 1;
                }else {
                    dp[i][j] = max3(dp[i-1][j-1],dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        return dp[l1][l2];
    }

    private int max3(int i, int i1, int i2) {
        int ans = i;
        if (i1 > ans) ans = i1;
        if (i2 > ans) ans = i2;
        return ans;
    }
}

416. 分割等和子集

中等

給定一個只包含正整數非空陣列。是否可以將這個陣列分割成兩個子集,使得兩個子集的元素和相等。

注意:

  1. 每個陣列中的元素不會超過 100
  2. 陣列的大小不會超過 200

示例 1:

輸入: [1, 5, 11, 5]

輸出: true

解釋: 陣列可以分割成 [1, 5, 5] 和 [11].

示例 2:

輸入: [1, 2, 3, 5]

輸出: false

解釋: 陣列不能分割成兩個元素和相等的子集.
class Solution { //回溯法,1ms
    public boolean canPartition(int[] nums) {
        int sum = 0;
        for (int num : nums) sum += num;
        if (sum % 2 == 1) return false;
        sum /= 2;
        return helper(nums,sum,0);
    }

    private boolean helper(int[] nums, int target, int index){
        if (target == 0) return true;
        if (index == nums.length || target < 0) return false;
        if (helper(nums, target-nums[index], index+1))
            return true; //有一種成了就返回true
        int j = index + 1;
        while (j < nums.length && nums[index] == nums[j]) j++;
        return helper(nums , target, j); //換個開頭
    }
}
class Solution { //DP,13ms
    boolean result = false;
    public boolean canPartition(int[] nums) {
        int sum = 0;
        for (int num : nums) sum += num;
        if (sum % 2 == 1) return false;
        sum /= 2;
        boolean[] dp = new boolean[sum+1];
        dp[0] = true;
        for (int num : nums){
            for (int i = sum; i > 0; i--){
                if (i >= num){
                    dp[i] = dp[i] || dp[i-num];
                }
            }
            if (dp[sum]) return true;
        }
        return false;
    }
}

494. 目標和

中等

給定一個非負整數陣列,a1, a2, ..., an, 和一個目標數,S。現在你有兩個符號 +-。對於陣列中的任意一個整數,你都可以從 +-中選擇一個符號新增在前面。

返回可以使最終陣列和為目標數 S 的所有新增符號的方法數。

示例:

輸入:nums: [1, 1, 1, 1, 1], S: 3
輸出:5
解釋:

-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3

一共有5種方法讓最終目標和為3。

提示:

  • 陣列非空,且長度不會超過 20 。
  • 初始的陣列的和不會超過 1000 。
  • 保證返回的最終結果能被 32 位整數存下。
class Solution { //回溯法,573ms
    int res = 0;
    public int findTargetSumWays(int[] nums, int S) {
        helper(nums,0,S);
        return res;
    }

    private void helper(int[] nums, int index, int target){
        if (index == nums.length){
            if (target == 0) res++;
            return;
        }
        helper(nums, index+1, target+nums[index]);
        helper(nums, index+1, target-nums[index]);
    }
}
class Solution { //DP,13ms
    public int findTargetSumWays(int[] nums, int S) {
        int sum = 0;
        for (int num : nums) sum += num;
        if (sum < S) return 0;
        int[][] dp = new int[nums.length][sum * 2 + 1];
        for (int i = 0; i < sum*2+1; i++){
            if (nums[0] == i-sum || -nums[0] == i-sum){
                dp[0][i] = 1;
            }
        }
        for (int i = 1; i < nums.length; i++){
            for (int j = 0; j < dp[0].length; j++){
                int a = j-nums[i] >= 0 ? dp[i-1][j-nums[i]] : 0;
                int b = j+nums[i] < dp[0].length ? dp[i-1][j+nums[i]] : 0;
                dp[i][j] = a + b;
            }
        }
        int k = nums[0] == 0 ? 2 : 1; //第一位是0,+-兩種情況,少算一半
        return dp[nums.length-1][S + sum] * k;
    }
}

474. 一和零

中等

在計算機界中,我們總是追求用有限的資源獲取最大的收益。

現在,假設你分別支配著 m0n1。另外,還有一個僅包含 01 字串的陣列。

你的任務是使用給定的 m0n1 ,找到能拼出存在於陣列中的字串的最大數量。每個 01 至多被使用一次

注意:

  1. 給定 01 的數量都不會超過 100
  2. 給定字串陣列的長度不會超過 600

示例 1:

輸入: Array = {"10", "0001", "111001", "1", "0"}, m = 5, n = 3
輸出: 4

解釋: 總共 4 個字串可以通過 5 個 0 和 3 個 1 拼出,即 "10","0001","1","0" 。

示例 2:

輸入: Array = {"10", "0", "1"}, m = 1, n = 1
輸出: 2

解釋: 你可以拼出 "10",但之後就沒有剩餘數字了。更好的選擇是拼出 "0" 和 "1" 。
class Solution { //回溯法,超時了
    private int res = 0;
    public int findMaxForm(String[] strs, int m, int n) {
        int[][] arr01 = generate(strs);
        helper(arr01,0,m,n,0);
        return res;
    }

    private void helper(int[][] arr01, int index, int m, int n, int count) {
        boolean no = m < 0 || n < 0;
        if (no || index >= arr01.length){
            if (no) count--;
            if (count > res) res = count;
            return;
        }
        helper(arr01,index+1,m-arr01[index][0],n-arr01[index][1],count+1);
        int j = index+1;
        while (j < arr01.length && arr01[j][0] == arr01[index][0] && arr01[j][1] == arr01[index][1]) j++;
        helper(arr01,index+1,m,n,count);
    }

    private int[][] generate(String[] strs) {
        int len = strs.length;
        int[][] arr01 = new int[len][2];
        for (int i = 0; i < len; i++) {
            arr01[i] = strTo01(strs[i]);
        }
        return arr01;
    }

    private int[] strTo01(String str) {
        int zero = 0;
        int one = 0;
        for (int i = 0; i < str.length(); i++) {
            if (str.charAt(i) == '0') zero++;
            else one++;
        }
        return new int[]{zero,one};
    }
}
class Solution { //DP,38ms
    public int findMaxForm(String[] strs, int m, int n) {
        int[][] dp = new int[m+1][n+1];
        for (String str : strs){
            int x = 0;
            int y = 0;
            for (int i = 0; i < str.length(); i++) {
                if (str.charAt(i) == '0') x++;
                else y++;
            }
            for (int i = m; i >= x; i--){ //倒著來
                for (int j = n; j >= y; j--){
                    dp[i][j] = Math.max(dp[i-x][j-y]+1,dp[i][j]);
                }
            }
        }
        return dp[m][n];
    }
}

322. 零錢兌換

中等

給定不同面額的硬幣 coins 和一個總金額 amount。編寫一個函式來計算可以湊成總金額所需的最少的硬幣個數。如果沒有任何一種硬幣組合能組成總金額,返回 -1

示例 1:

輸入: coins = [1, 2, 5], amount = 11
輸出: 3 
解釋: 11 = 5 + 5 + 1

示例 2:

輸入: coins = [2], amount = 3
輸出: -1

說明:
你可以認為每種硬幣的數量是無限的。

public class Solution { //抄了答案
    public int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount + 1];
        Arrays.fill(dp, amount + 1); //記住這個函式
        dp[0] = 0;
        for (int i = 1; i <= amount; i++) {
            for (int coin : coins) { //想起 醜數 那道題
                if (i - coin >= 0 && dp[i - coin] != amount + 1) {
                    //後面布林的意思:前面那個都不能表示,更別說這個了
                    dp[i] = Math.min(dp[i], 1 + dp[i - coin]);
                }
            }
        }
        return dp[amount] == amount + 1 ? -1 : dp[amount];
    }
}

518. 零錢兌換 II

中等

給定不同面額的硬幣和一個總金額。寫出函式來計算可以湊成總金額的硬幣組合數。假設每一種面額的硬幣有無限個。

示例 1:

輸入: amount = 5, coins = [1, 2, 5]
輸出: 4
解釋: 有四種方式可以湊成總金額:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

示例 2:

輸入: amount = 3, coins = [2]
輸出: 0
解釋: 只用面額2的硬幣不能湊成總金額3。

示例 3:

輸入: amount = 10, coins = [10] 
輸出: 1

注意:

你可以假設:

  • 0 <= amount (總金額) <= 5000
  • 1 <= coin (硬幣面額) <= 5000
  • 硬幣種類不超過 500 種
  • 結果符合 32 位符號整數
class Solution { //超時的回溯法
    Set<List<Integer>> set = new HashSet<>();
    List<Integer> list = new ArrayList<>();
    public int change(int amount, int[] coins) {
        helper(amount,coins);
        return set.size();
    }

    private void helper(int amount, int[] coins){
        if (amount == 0) set.add(sort(list));
        if (amount <= 0) return;
        for (int num : coins) {
            list.add(num);
            helper(amount - num, coins);
            list.remove(list.size()-1);
        }
    }

    private List<Integer> sort(List<Integer> list) {
        List<Integer> ans = new ArrayList<>(list);
        Collections.sort(ans);
        return ans;
    }
}
class Solution { //DP
    public int change(int amount, int[] coins) {
        int[] dp = new int[amount+1];
        dp[0] = 1;
        for (int coin : coins){
            for (int i = 1; i < amount+1; i++){
                if (i >= coin) dp[i] += dp[i-coin];
            }
        }
        return dp[amount];
    }
}

139. 單詞拆分

中等

給定一個非空字串 s 和一個包含非空單詞列表的字典 wordDict,判定 s 是否可以被空格拆分為一個或多個在字典中出現的單詞。

說明:

  • 拆分時可以重複使用字典中的單詞。
  • 你可以假設字典中沒有重複的單詞。

示例 1:

輸入: s = "leetcode", wordDict = ["leet", "code"]
輸出: true
解釋: 返回 true 因為 "leetcode" 可以被拆分成 "leet code"。

示例 2:

輸入: s = "applepenapple", wordDict = ["apple", "pen"]
輸出: true
解釋: 返回 true 因為 "applepenapple" 可以被拆分成 "apple pen apple"。
     注意你可以重複使用字典中的單詞。

示例 3:

輸入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
輸出: false
class Solution { //10ms
    public boolean wordBreak(String s, List<String> wordDict) {
        int len = s.length();
        boolean[] dp = new boolean[len+1];
        Set<String> set = new HashSet<>(wordDict);
        dp[0] = true;
        for (int l = 0; l < len; l++)
            for (int r = l+1; r <= len; r++)
                if (dp[l] && set.contains(s.substring(l,r)))
                    dp[r] = true;
        return dp[len];
    }
}
class Solution { //5ms
    public boolean wordBreak(String s, List<String> wordDict) {
        int len = s.length();
        boolean[] dp = new boolean[len+1];
        dp[0] = true;
        for (int l = 0; l < len; l++){
            if (!dp[l]) continue;
            for (String word : wordDict){
                int r = l + word.length();
                if (r <= len && s.substring(l,r).equals(word)){
                    dp[r] = true;
                }
            }
        } 
        return dp[len];
    }
}

377. 組合總和 Ⅳ

中等

給定一個由正整陣列成且不存在重複數字的陣列,找出和為給定目標正整數的組合的個數。

示例:

nums = [1, 2, 3]
target = 4

所有可能的組合為:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)

請注意,順序不同的序列被視作不同的組合。

因此輸出為 7。

進階:
如果給定的陣列中含有負數會怎麼樣?
問題會產生什麼變化?
我們需要在題目中新增什麼限制來允許負數的出現?

class Solution {
    public int combinationSum4(int[] nums, int target) {
        int n = nums.length;
        int[] dp = new int[target+1];
        dp[0] = 1;
        for (int i = 1; i < target+1; i++){
            for (int num : nums){
                if (i >= num){
                    dp[i] += dp[i-num];
                }
            }
        }
        return dp[target];
    }
}

309. 最佳買賣股票時機含冷凍期

中等

給定一個整數陣列,其中第 i 個元素代表了第 i 天的股票價格 。

設計一個演算法計算出最大利潤。在滿足以下約束條件下,你可以儘可能地完成更多的交易(多次買賣一支股票):

  • 你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。
  • 賣出股票後,你無法在第二天買入股票 (即冷凍期為 1 天)。

示例:

輸入: [1,2,3,0,2]
輸出: 3 
解釋: 對應的交易狀態為: [買入, 賣出, 冷凍期, 買入, 賣出]
class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        if (n < 2) return 0;
        int[][] dp = new int[n][2];
        dp[0][1] = - prices[0];
        for (int i = 1; i < n; i++){
            dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i]);
            dp[i][1] = Math.max(dp[i-1][1], (i >= 2 ? dp[i-2][0] : 0) - prices[i]);
        }
        return Math.max(dp[n-1][0],dp[n-1][1]);
    }
}

714. 買賣股票的最佳時機含手續費

中等

給定一個整數陣列 prices,其中第 i 個元素代表了第 i 天的股票價格 ;非負整數 fee 代表了交易股票的手續費用。

你可以無限次地完成交易,但是你每筆交易都需要付手續費。如果你已經購買了一個股票,在賣出它之前你就不能再繼續購買股票了。

返回獲得利潤的最大值。

注意:這裡的一筆交易指買入持有並賣出股票的整個過程,每筆交易你只需要為支付一次手續費。

示例 1:

輸入: prices = [1, 3, 2, 8, 4, 9], fee = 2
輸出: 8
解釋: 能夠達到的最大利潤:  
在此處買入 prices[0] = 1
在此處賣出 prices[3] = 8
在此處買入 prices[4] = 4
在此處賣出 prices[5] = 9
總利潤: ((8 - 1) - 2) + ((9 - 4) - 2) = 8.
class Solution { //手續費分開交,36ms
    public int maxProfit(int[] prices, int fee) {
        int n = prices.length;
        double[][] dp = new double[n][2];
        double feeForOne = 0.5 * fee;
        dp[0][1] = 0.0 - prices[0] - feeForOne;
        for (int i = 1; i < n; i++){
            dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1]+prices[i]-feeForOne);
            dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0]-prices[i]-feeForOne);
        }
        return (int)Math.max(dp[n-1][0],dp[n-1][1]);
    }
}
class Solution { //賣的時候交,28ms
    public int maxProfit(int[] prices, int fee) {
        int n = prices.length;
        int[][] dp = new int[n][2];
        dp[0][1] = - prices[0];
        for (int i = 1; i < n; i++){
            dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1]+prices[i]-fee);
            dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0]-prices[i]);
        }
        return Math.max(dp[n-1][0],dp[n-1][1]);
    }
}

123. 買賣股票的最佳時機 III

困難

給定一個數組,它的第 i 個元素是一支給定的股票在第 i 天的價格。

設計一個演算法來計算你所能獲取的最大利潤。你最多可以完成 兩筆 交易。

注意: 你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。

示例 1:

輸入: [3,3,5,0,0,3,1,4]
輸出: 6
解釋: 在第 4 天(股票價格 = 0)的時候買入,在第 6 天(股票價格 = 3)的時候賣出,這筆交易所能獲得利潤 = 3-0 = 3 。
     隨後,在第 7 天(股票價格 = 1)的時候買入,在第 8 天 (股票價格 = 4)的時候賣出,這筆交易所能獲得利潤 = 4-1 = 3 。

示例 2:

輸入: [1,2,3,4,5]
輸出: 4
解釋: 在第 1 天(股票價格 = 1)的時候買入,在第 5 天 (股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。   
     注意你不能在第 1 天和第 2 天接連購買股票,之後再將它們賣出。   
     因為這樣屬於同時參與了多筆交易,你必須在再次購買前出售掉之前的股票。

示例 3:

輸入: [7,6,4,3,1] 
輸出: 0 
解釋: 在這個情況下, 沒有交易完成, 所以最大利潤為 0。
class Solution { //不是通解,下道題通解
    public int maxProfit(int[] prices) {
        if (prices == null || prices.length < 2) return 0;

        int len = prices.length;
        int[] left = new int[len]; //從左往右更新最小值
        int[] right = new int[len]; //從右往左更新最大值
        left[0] = prices[0];
        right[len-1] = prices[len-1];
        for (int i = 1; i < len; i++)
            left[i] = Math.min(left[i-1],prices[i]);
        for (int i = len-2; i >= 0; i--)
            right[i] = Math.max(right[i+1],prices[i]);

        for (int i = 0; i < len; i++){
            left[i] = prices[i] - left[i]; //之前買入,今天賣出的最大利潤
            right[i] = right[i] - prices[i]; //今天買入,之後賣出的最大利潤
        }

        for (int i = 1; i < len; i++)
            left[i] = Math.max(left[i],left[i-1]); //前半部分的最大利潤
        for (int i = len-2; i >= 0; i--)
            right[i] = Math.max(right[i],right[i+1]); //後半部分的最大利潤

        int ans = 0;
        for (int i = 0; i < len; i++)
            ans = Math.max(left[i] + right[i], ans);
        return ans;
    }
}

188. 買賣股票的最佳時機 IV

困難

給定一個數組,它的第 i 個元素是一支給定的股票在第 i 天的價格。

設計一個演算法來計算你所能獲取的最大利潤。你最多可以完成 k 筆交易。

注意: 你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。

示例 1:

輸入: [2,4,1], k = 2
輸出: 2
解釋: 在第 1 天 (股票價格 = 2) 的時候買入,在第 2 天 (股票價格 = 4) 的時候賣出,這筆交易所能獲得利潤 = 4-2 = 2 。

示例 2:

輸入: [3,2,6,5,0,3], k = 2
輸出: 7
解釋: 在第 2 天 (股票價格 = 2) 的時候買入,在第 3 天 (股票價格 = 6) 的時候賣出, 這筆交易所能獲得利潤 = 6-2 = 4 。
     隨後,在第 5 天 (股票價格 = 0) 的時候買入,在第 6 天 (股票價格 = 3) 的時候賣出, 這筆交易所能獲得利潤 = 3-0 = 3 。
class Solution {
    public int maxProfit(int k, int[] prices) {
        int n = prices.length;
        if (n < 2 || k < 1) return 0;
        if (k > n/2) return maxProfitII(prices); //k不起約束作用了
        int[][] dp = new int[k][2]; //0買 1賣
        for (int i = 0; i < k; i ++){
            dp[i][0] = Integer.MIN_VALUE;
        }
        for (int price : prices){
            dp[0][0] = Math.max(dp[0][0], - price);
            dp[0][1] = Math.max(dp[0][1], dp[0][0] + price);
            for (int i = 1; i < k; i ++){
                dp[i][0] = Math.max(dp[i][0], dp[i-1][1] - price); //第i-1次買了之後,才能買第i次
                dp[i][1] = Math.max(dp[i][1], dp[i][0] + price);
            }
        }
        return dp[k-1][1];
    }

    private int maxProfitII(int [] prices){
        int ans = 0;
        for (int i = 1; i < prices.length; i++){
            if (prices[i] > prices[i-1])
                ans += prices[i] - prices[i-1];
        }
        return ans;
    }
}

583. 兩個字串的刪除操作

中等

給定兩個單詞 word1word2,找到使得 word1word2 相同所需的最小步數,每步可以刪除任意一個字串中的一個字元。

示例:

輸入: "sea", "eat"
輸出: 2
解釋: 第一步將"sea"變為"ea",第二步將"eat"變為"ea"

提示:

  1. 給定單詞的長度不超過500。
  2. 給定單詞中的字元只含有小寫字母。
class Solution {
    public int minDistance(String word1, String word2) {
        int m = word1.length();
        int n = word2.length();
        if (m == 0) return n;
        if (n == 0) return m;
        int[][] dp = new int[m+1][n+1];
        for (int i = 1; i < m + 1; i++){
            for (int j = 1; j < n + 1; j++){
                if (word1.charAt(i-1) == word2.charAt(j-1)){
                    dp[i][j] = dp[i-1][j-1] + 1;
                }else{
                    dp[i][j] = Math.max(dp[i][j-1], dp[i-1][j]);
                }
            }
        }
        return m + n - dp[m][n] * 2;
    }
}

650. 只有兩個鍵的鍵盤

中等

最初在一個記事本上只有一個字元 'A'。你每次可以對這個記事本進行兩種操作:

  1. Copy All (複製全部) : 你可以複製這個記事本中的所有字元(部分的複製是不允許的)。
  2. Paste (貼上) : 你可以貼上你上一次複製的字元。

給定一個數字 n 。你需要使用最少的操作次數,在記事本中打印出恰好 n 個 'A'。輸出能夠打印出 n 個 'A' 的最少操作次數。

示例 1:

輸入: 3
輸出: 3
解釋:
最初, 我們只有一個字元 'A'。
第 1 步, 我們使用 Copy All 操作。
第 2 步, 我們使用 Paste 操作來獲得 'AA'。
第 3 步, 我們使用 Paste 操作來獲得 'AAA'。

說明:

  1. n 的取值範圍是 [1, 1000] 。
class Solution {
    public int minSteps(int n) {
        int[] dp = new int[n+1];
        for (int i = 2; i < n+1; i++) dp[i] = i;
        for (int i = 2; i < (n+1)/2; i++){
            int k = 2;
            dp[k * i] = dp[i] + 2;
            k++;
            while (k * i < n+1){
                dp[k*i] = dp[(k-1)*i] + 1;
                k++;
            }
        }
        return dp[n];
    }
}

72. 編輯距離

困難

給你兩個單詞 word1word2,請你計算出將 word1 轉換成 word2 所使用的最少運算元 。

你可以對一個單詞進行如下三種操作:

  1. 插入一個字元
  2. 刪除一個字元
  3. 替換一個字元

示例 1:

輸入:word1 = "horse", word2 = "ros"
輸出:3
解釋:
horse -> rorse (將 'h' 替換為 'r')
rorse -> rose (刪除 'r')
rose -> ros (刪除 'e')

示例 2:

輸入:word1 = "intention", word2 = "execution"
輸出:5
解釋:
intention -> inention (刪除 't')
inention -> enention (將 'i' 替換為 'e')
enention -> exention (將 'n' 替換為 'x')
exention -> exection (將 'n' 替換為 'c')
exection -> execution (插入 'u')
class Solution {
    public int minDistance(String word1, String word2) {
        int m = word1.length();
        int n = word2.length();
        char[] chars1 = word1.toCharArray();
        char[] chars2 = word2.toCharArray();
        int[][] dp = new int[m+1][n+1];
        for (int i = m-1; i >= 0; i--)
            dp[i][n] = dp[i+1][n] + 1; //只刪除
        for (int i = n-1; i >= 0; i--)
            dp[m][i] = dp[m][i+1] + 1; //只刪除
        for (int i = m-1; i >= 0; i--){
            for (int j = n-1; j >= 0; j--){
                if (chars1[i] == chars2[j]) {
                    dp[i][j] = dp[i+1][j+1]; //相同則縮小子問題
                }else{
                    dp[i][j] = 1 + Math.min(dp[i+1][j],Math.min(dp[i+1][j+1],dp[i][j+1]));
                }
            }
        }
        return dp[0][0];
    }
}

頻率排序

903,552,546,410,943,471,466,887,5,85,920,300,486,53,818,1024,312,639,354,464,975,871,321,741,847,718,673,1147,70,10,361,873,983,1092,714,1074,1012,121,898,698,97,44,688,801,91,221,838,403