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

posted @ 2020-07-31 17:14  鹏懿如斯  阅读(496)  评论(0编辑  收藏  举报