Leetcode分类刷算法之动态规划专题

    • 1. leetcode198 打家劫舍
    • 2. leetcode 213 打家劫舍II
    • 3. leetcode337 打家劫舍 III
    • 4.leetcode 121 买卖股票的最佳时机
    • 5. leetcode 122. 买卖股票的最佳时机 II
    • 6. leetcode 309 最佳买卖股票时机含冷冻期
    • 7.leetcode 714. 买卖股票的最佳时机含手续费
    • 8.leetcode 123. 买卖股票的最佳时机 III
    • 9.leetcode 188 买卖股票的最佳时机 IV
    • 10. leetcode 279 prefect squares
    • 11.leetcode 72 edit distance
    • 12 leecode 62. 不同路径
    • 13.leecode 63. 不同路径 II
    • 14.leetcode 322. 零钱兑换
    • 15.leetcode 518. 零钱兑换 II
    • 16.leetcode32 最长的有效括号
    • 17.leetcode 64 最小路径和
    • 18.leetcode 91. 解码方法
    • 19.leetcode 221. 最大正方形
    • 20.leetcode 647. 回文子串
    • 21.leetcode 403. 青蛙过河
    • 22.leetcode 552. 学生出勤记录 II
    • 23.三角形最小路径和
    • 24.最大子序和
    • 25.最大连续子序列积
    • 26.最长上升子序列
    • 27.分割回文串 II
    • 28. 交错字符串
    • 29. 单词拆分
    • 30. 单词拆分 II
    • 31. 不同的子序列
    • 32. 二维区域和检索 - 矩阵不可变

1. leetcode198 打家劫舍

198. 打家劫舍
dp[i]表示当前位置能偷到的最大值
max(当前位置偷+前两个位置的最大值,当前位置不偷即前一个位置的最大值)

dp[i] = Math.max(dp[i - 2]+ nums[i],dp[i-1]);

public int rob(int[] nums) {
        if (nums == null || nums.length <= 0){
            return 0;
        }
        if (nums.length == 1){
            return nums[0];
        }
        int[] dp = new int[nums.length];
        dp[0] = nums[0];
        dp[1] = Math.max(nums[0],nums[1]);
        for (int i = 2; i  < nums.length; i++) {
            dp[i] = Math.max(dp[i - 2]+ nums[i],dp[i-1]);
        }
        return dp[nums.length - 1];
    }

2. leetcode 213 打家劫舍II

于上一题不同的是是,所有的房屋围城了一圈,行成了环状,这就意味着偷了第一家,最后一家就不能偷了,偷了最后一家,第一家就不能偷了。可以分解成两个问题,偷nums[0:n-1]和偷nums[1:n],这两个之间的最大值。所以是两个动态规划数组的问题。

public int rob(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        if (nums.length == 1) {
            return nums[0];
        }
        if (nums.length == 2){
            return Math.max(nums[0],nums[1]);
        }
        int[] dp1 = new int[nums.length];//偷nums[0:n-1]
        int[] dp2 = new int[nums.length];//偷nums[1:n]
        dp1[0] = nums[0];
        dp1[1] = Math.max(nums[0],nums[1]);
        for (int i = 2; i < nums.length - 1; i++) {
            dp1[i] = Math.max(dp1[i-2] + nums[i], dp1[i-1]);
        }
        dp2[1] = nums[1];
        dp2[2] = Math.max(nums[2],nums[1]); 
        for (int i = 3;i < nums.length; i++) {
            dp2[i] = Math.max(dp2[i-2] + nums[i], dp2[i-1]);
        }
        return Math.max(dp1[nums.length - 2],dp2[nums.length - 1]);
    }

3. leetcode337 打家劫舍 III

leetcode337 打家劫舍 III
树形dp问题

public int rob(TreeNode root) {
        if ( root == null){
            return 0;
        }
        //偷父节点:父节点的值 + 偷孩子节点的左右孩子节点
        int robRoot = root.val;
        if(root.left != null){
            robRoot += rob(root.left.left) + rob(root.left.right);
        }
        if(root.right != null) {
            robRoot += rob(root.right.left) + rob(root.right.right);
        }
        //不偷父节点: 偷左右孩子节点
        int robChild = rob(root.left) + rob(root.right);
        return Math.max(robRoot,robChild);
    }

4.leetcode 121 买卖股票的最佳时机

121. 买卖股票的最佳时机

很好的一篇题解:一个方法团灭 6 道股票问题

public int maxProfit(int[] prices) {
        if (prices == null || prices.length == 0){
            return 0;
        }
        int n = prices.length;
        int[][]dp = new int[n][2];
        dp[0][0] = 0;
        dp[0][1] = - prices[0];
        for(int i = 1; i < n; i++){
            //今天手上没有持有股票的情况,1.前一天手上也没有持有股票 2.前一天持有股票 今天卖出
            dp[i][0] = Math.max(dp[i - 1][0],dp[i - 1][1] + prices[i]);
            //今天手上持有股票的情况,1.前一天手上也持有股票,今天不变 2.前一天手上没有持有股票,今天买入。
            dp[i][1] = Math.max(dp[i - 1][1], - prices[i]);
        }
        return dp[n - 1][0];
    }

空间复杂度O(1)的解法

public int maxProfit(int[] prices) {
        if (prices == null || prices.length == 0){
            return 0;
        }
        int n = prices.length;
        int dp_i_0 = 0; //开始的那一天手上未持有股票
        int dp_i_1 = - prices[0]; //开始的那一天手上持有股票 利润是-prices[0]
        for (int i = 0; i < n; i++) {
            dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]); 
            dp_i_1 = Math.max(dp_i_1, - prices[i]); //只要今天买入 手上的利润就是-prices[i] 因为限制了只进行一次交易
        }
        return dp_i_0;
    }

5. leetcode 122. 买卖股票的最佳时机 II

买卖股票的最佳时机 II
不限制交易次数

public int maxProfit(int[] prices) {
        if (prices == null || prices.length == 0){
            return 0;
        }
        int n = prices.length;
        int[][] dp = new int[n][2];
        dp[0][0] = 0;
        dp[0][1] = - prices[0];
        for (int i = 1; i < prices.length; 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], dp[i - 1][0] - prices[i]);
        }
        return dp[n-1][0];
    }

空间复杂度O(1)


 public int maxProfit(int[] prices) {
        if (prices == null || prices.length == 0){
            return 0;
        }
        int n = prices.length;
        int dp_i_0 = 0;
        int dp_i_1 = - prices[0];
        for (int i = 0; i < prices.length; i++) {
            int temp = dp_i_0;
            dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]);
            dp_i_1 = Math.max(dp_i_1, temp - prices[i]);      
        }
        return dp_i_0;
    }

6. leetcode 309 最佳买卖股票时机含冷冻期

309. 最佳买卖股票时机含冷冻期

每次sell掉之后要等一天才能继续交易

public int maxProfit(int[] prices) {
        if (prices == null || prices.length == 0){
            return 0;
        }
        if(prices.length == 1){
            return 0;
        }
        int n = prices.length;
        int[][] dp = new int[n][2];
        dp[0][0] = 0; 
        dp[0][1] = - prices[0];
        dp[1][0] = Math.max(0,dp[0][1] + prices[1]);     
        dp[1][1] = Math.max(dp[0][1], - prices[1]); 
        for (int i = 2; 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], dp[i - 2][0] - prices[i]);
        }
        return dp[n-1][0];
    }
public int maxProfit(int[] prices) {
        if (prices == null || prices.length == 0){
            return 0;
        }
        int n =prices.length;
        int dp_i_0 = 0;
        int dp_i_1 = - prices[0];
        int dp_pre_0 = 0; //代表dp[i-2][0] 前天卖出 今天买入
        for (int i = 0; i < n; i++) {
            int temp = dp_i_0;//昨天未持有股票
            dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]); //今天未持有
            dp_i_1 = Math.max(dp_i_1, dp_pre_0 - prices[i]); //今天持有
            dp_pre_0 = temp; // 前天未持有
        }
        return dp_i_0;
    }

7.leetcode 714. 买卖股票的最佳时机含手续费

714. 买卖股票的最佳时机含手续费

public int maxProfit(int[] prices, int fee) {
        if (prices == null || prices.length == 0){
            return 0;
        }
        int n = prices.length;
        int[][] dp = new int[n][2];
        dp[0][0] = 0;
        dp[0][1] = - prices[0] - fee;//减去税
        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], dp[i-1][0] - prices[i] - fee); //减去税
        }
        return dp[n-1][0];
    }
public int maxProfit(int[] prices, int fee) {
        if (prices == null || prices.length == 0){
            return 0;
        }
        int n = prices.length;
        int dp_i_0 = 0;
        int dp_i_1 = - prices[0] - fee;
        for (int i = 0; i < n; i++) {
            int temp = dp_i_0;
            dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]);
            dp_i_1 = Math.max(dp_i_1, temp - prices[i] - fee);
        }
        return dp_i_0;
    }

8.leetcode 123. 买卖股票的最佳时机 III

最多只能进行两次交易 相当于又增加了一个交易次数的这个维度
123. 买卖股票的最佳时机 III

public int maxProfit(int[] prices) {
        if (prices == null || prices.length == 0){
            return 0;
        }
        int max_k = 2;
        int n = prices.length;
        int[][][] dp = new int[n][max_k + 1][2];
        dp[0][2][0] = 0;
        dp[0][2][1] = - prices[0];
        dp[0][1][0] = 0;
        dp[0][1][1] = - prices[0];
        for (int i = 1; i < n; i++) {
            for (int k = max_k; k >= 1; k--) {
                dp[i][k][0] = Math.max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i]);
                dp[i][k][1] = Math.max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i]);
            }
        }
        return dp[n - 1][max_k][0];//交易了两次的利润最大
    }
public int maxProfit(int[] prices) {
        if (prices == null || prices.length == 0){
            return 0;
        }
        int n = prices.length;
        int dp_i_2_0 = 0;
        int dp_i_2_1 = - prices[0];
        int dp_i_1_0 = 0;
        int dp_i_1_1 = - prices[0];
        for (int i = 1; i < n; i++) {
            dp_i_2_0 = Math.max(dp_i_2_0, dp_i_2_1 + prices[i]);
            dp_i_2_1 = Math.max(dp_i_2_1, dp_i_1_0 - prices[i]);
            dp_i_1_0 = Math.max(dp_i_1_0, dp_i_1_1 + prices[i]);
            dp_i_1_1 = Math.max(dp_i_1_1, - prices[i]);
        }
        return dp_i_2_0;
    }

9.leetcode 188 买卖股票的最佳时机 IV

188. 买卖股票的最佳时机 IV
一次交易由买入和卖出,至少需要两天,所以说有效的限制了k应该不超过n/2,如果超过就表示没有约束作用了。

public int maxProfit(int max_k, int[] prices) {
        if (prices == null || prices.length == 0 || max_k <= 0){
            return 0;
        }
        int n = prices.length;
        if (max_k  > n/2){
            return maxProfit_k_info(prices);
        }
        int[][][] dp = new int[n][max_k + 1][2];
        for (int i = 0; i < n; i++){
            for (int k = max_k; k >=1; k--) {
                if ( i - 1 == -1) {
                    dp[i][k][0] = 0;
                    dp[i][k][1] = - prices[0];
                    continue;
                }
                dp[i][k][0] = Math.max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i]);
                dp[i][k][1] = Math.max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i]);
            }
        }
        return dp[n - 1][max_k][0];
    }

    public int maxProfit_k_info(int[] prices) {
        int n = prices.length;
        int dp_i_0 = 0;
        int dp_i_1 = - prices[0];
        for (int i = 1;i < prices.length; i++) {
            int temp = dp_i_0;
            dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]);
            dp_i_1 = Math.max(dp_i_1, temp  - prices[i]);
        }
        return dp_i_0;
    }

10. leetcode 279 prefect squares

279. 完全平方数

dp[i] = MIN(dp[i], dp[i - j * j] + 1)
public int numSquares(int n) {
        if (n <= 0) {
            return 0;
        }
        int[] dp = new int[n + 1];
        for (int i = 0; i <= n; i++){
            dp[i] = i;
            for (int j = 1; i - j*j >=0; j++) {
                dp[i] = Math.min(dp[i], dp[i - j*j] + 1);
            }
        }
        return dp[n];
    }

11.leetcode 72 edit distance

72. 编辑距离

如果word1[i] 与Word2[j]相同 则
dp[i][j]= dp[i-1][j-1]
如果不同 
dp[i][j] = min( 可以通过dp[i-1][j-1]让做replace
                可以通过在dp[i-1][j]上做insert
                可以在dp[i][j-1]上做delete )
public int minDistance(String word1, String word2) {
        if (word1 == null || word2 == null) {
            return 0;
        }
        int m = word2.length() + 1;
        int n = word1.length() + 1;
        int[][] dp = new int[m][n];
        for (int i = 0; i < n ; i++) {
            dp[0][i] = i;
        }
        for (int i = 0; i < m ; i++) {
            dp[i][0] = i;
        }
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                if (word2.charAt(i - 1) == word1.charAt(j - 1)){
                    dp[i][j] = dp[i - 1][j - 1];
                }else{
                    dp[i][j] = Math.min(dp[i - 1][j - 1] + 1,dp[i - 1][j] + 1);
                    dp[i][j] = Math.min(dp[i][j],dp[i][j - 1] + 1);
                }
            }
        }
        return dp[m - 1][n - 1];
    }

12 leecode 62. 不同路径

62. 不同路径

public int uniquePaths(int m, int n) {
        int[][] dp = new int[m][n];
        for (int i = 0; i <= m -1; i++){
            dp[i][n - 1] = 1;
        }
        for (int i = 0; i <= n -1; i++){
            dp[m - 1][i] = 1;
        }
        for (int i = m - 2; i >= 0; i--){
            for ( int j = n - 2; j >= 0; j--) {
                dp[i][j] = dp[i+1][j] + dp[i][j+1];
            }
        }
        return dp[0][0];
    }
 public int uniquePaths(int m, int n) {
        if ( m <=0 || n <= 0){
            return 0;
        }
        int[] pre = new int[n];
        int[] cur = new int[n];
        Arrays.fill(pre,1);
        Arrays.fill(cur,1);
        for(int i = 1; i < m ; i++) { //循环次数
            for(int j = 1; j < n; j++) {
                cur[j] = cur[j-1] + pre[j];
            }
            pre = cur.clone();
        }
        return cur[n-1];
    }

13.leecode 63. 不同路径 II

62. 不同路径

public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        int m = obstacleGrid.length;
        int n = obstacleGrid[0].length;
        int[] pre = new int[n];
        int[] cur = new int[n];
        for (int i = 0; i < n; i++){
            if (obstacleGrid[0][i] == 1){
                pre[i] = 0;
                break;
            }else{
                pre[i] = 1;
            }
        }
        Arrays.fill(cur, 1);
        if(obstacleGrid[1][0] == 1) cur[0] = 0;
        for (int i = 1; i < m ; i++){
            for(int j = 1 ; j < n; j++) {
                cur[j] = obstacleGrid[i][j] == 1 ? 0 : cur[j-1] + pre[j];
            }
            pre = cur.clone();
        }
        return cur[n-1];
    }
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        int m = obstacleGrid.length;
        int n = obstacleGrid[0].length;
        if(obstacleGrid[0][0] == 1 || obstacleGrid[m - 1][n - 1] == 1){
            return 0;
        }     
        int[][] dp = new int[m][n];
        dp[m-1][n-1] = 1;
        for (int i = m - 2; i >= 0 ; i--){
            if(obstacleGrid[i][n-1] == 1) {
                dp[i][n-1] = 0;
                break;
            }else{
                dp[i][n-1] = 1;
            }
        }
        for (int i = n - 2 ; i >= 0 ; i--){
            if(obstacleGrid[m-1][i] == 1) {
                dp[m - 1][i] = 0;
                break;
            }else{
                dp[m - 1][i] = 1;
            }
        }
        for(int i = m - 2; i >= 0; i--){
            for(int j = n -2; j >= 0; j--){
                dp[i][j] = obstacleGrid[i][j] == 1 ? 0 : dp[i+1][j]+dp[i][j+1];
            }
        }
        return dp[0][0];
    }

14.leetcode 322. 零钱兑换

322. 零钱兑换

public int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount + 1];
        for (int i = 1; i <= amount; i++) {
            dp[i] = Integer.MAX_VALUE;
            for (int coin : coins) {
                if (i - coin >= 0 && dp[i - coin] != Integer.MAX_VALUE) {
                    dp[i] = Math.min(dp[i - coin] + 1,dp[i]);
                }
            }
        }
        return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount];
    }

15.leetcode 518. 零钱兑换 II

518. 零钱兑换 II

public int change(int amount, int[] coins) {
        int[] dp = new int[amount + 1];
        dp[0] = 1;
        for(int  coin : coins) {
            for(int j = 1; j <= amount; j++) {
                if( j >= coin){
                    dp[j] = dp[j] + dp[j - coin]; 
                }
            }
        }
        return dp[amount];
    }

16.leetcode32 最长的有效括号

32. 最长有效括号

public int longestValidParentheses(String s) {
        int res = 0;
        if (s == null || s.length() == 0) {
            return res;
        }
        int[] dp = new int[s.length()];
        for (int i = 1; i < s.length(); i++) {
            if (s.charAt(i) == ')') {
                //右括号前面是左括号
                if (s.charAt(i - 1) == '(') {
                    dp[i] = i >= 2 ? dp[i - 2] + 2 : 2;
                //右括号前面是作括号,并且出去前边的合法序列的前边是左括号
                }else if (i - dp[i - 1] > 0 && s.charAt( i - dp[i - 1] - 1) == '(') {
                    dp[i] = dp[i - 1] + ((i - dp[i - 1]) >= 2 ? dp[i - dp[i - 1] - 2] : 0) + 2;
                }
                res = Math.max(res,dp[i]);
            }
        }
        return res;
    }

17.leetcode 64 最小路径和

64. 最小路径和

public int minPathSum(int[][] grid) {
        if (grid == null || grid.length == 0 || grid[0] == null || grid[0].length == 0) {
			return 0;
		}
        int m=grid.length-1;
        int n=grid[0].length-1;
        int dp[][]=new int[m+1][n+1];
        dp[m][n]=grid[m][n];
        for(int i=m-1;i>=0;i--){
            dp[i][n]=grid[i][n]+dp[i+1][n];
        }
        for(int j=n-1;j>=0;j--){
            dp[m][j]=grid[m][j]+dp[m][j+1];
        }
        for(int i=m-1;i>=0;i--){
            for(int j=n-1;j>=0;j--){
                dp[i][j]=grid[i][j]+Math.min(dp[i+1][j],dp[i][j+1]);
            }
        }
        return dp[0][0];
    }

18.leetcode 91. 解码方法

91. 解码方法

public int numDecodings(String s) {
        if (s == null || s.length() == 0){
            return 0;
        }
        int len  = s.length();
        int[] dp = new int[len + 1];
        dp[len] = 1;
        dp[len - 1] = s.charAt(len - 1) == '0' ? 0 : 1;
        for (int i = len - 2; i >= 0; i--) {
            if(s.charAt(i) == '0') {
                dp[i] = 0;
                continue;
            }
            if ((s.charAt(i) - '0') * 10 + (s.charAt(i + 1) - '0') <= 26) {
                dp[i] = dp[i + 1] + dp[i + 2];
            }else{
                dp[i] = dp[i + 1];
            }
        }
        return dp[0];
    }

19.leetcode 221. 最大正方形

221. 最大正方形

public int maximalSquare(char[][] matrix) {
        if (matrix == null || matrix.length == 0)return 0;
        int m = matrix.length;
        int n = matrix[0].length;
        int[][] dp = new int[m][n];
        int maxLength = 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (matrix[i][j] == '1') {
                    if (i == 0 || j == 0) {
                        dp[i][j] = 1;
                    }else{
                        dp[i][j] = Math.min(dp[i - 1][ j - 1], Math.min(dp[i - 1][j], dp[i][j - 1])) + 1;               
                    } 
                    maxLength = Math.max(maxLength, dp[i][j]);         
                }
            }
        }
        return maxLength * maxLength;
    }

20.leetcode 647. 回文子串

647. 回文子串

 public int countSubstrings(String s) {
        if (s == null) return 0;
        if (s.length() == 0 || s.length() == 1)return s.length();
        int n = s.length();
        boolean[][] dp = new boolean[n][n];
        int result = 0;
        for (int end = 0 ; end < n; end++) {
            for (int start = 0; start <= end; start++) {
                if (start == end) {
                    dp[start][end] = true;
                    result++;
                }else {
                    if (s.charAt(end) == s.charAt(start) && (end - start <= 1 || dp[start + 1][end - 1])) {
                        dp[start][end] = true;
                        result++;
                    }
                }
            }
        }
        return result;
    }

21.leetcode 403. 青蛙过河

403. 青蛙过河

public boolean canCross(int[] stones) {
        int n = stones.length;
        HashMap<Integer,HashSet<Integer>> dp = new HashMap<>();
        for (int i = 0; i < stones.length; i++) {
            dp.put(stones[i],new HashSet<>());
        }
        dp.get(stones[0]).add(0);//开始位置
        for (int i = 0; i < n; i++) {
            HashSet<Integer> set = dp.get(stones[i]); //0 : 0 
            for (Integer k : set) {
                for (int j = k - 1; j <= k + 1; j++) { //能跳的距离 j
                    if(j <= 0)continue;
                    if(dp.containsKey(stones[i] + j)) {  //能跳到的位置
                        dp.get(stones[i] + j).add(j); //加上当前跳的距离
                    }
                }
            }
        }
        return !dp.get(stones[n - 1]).isEmpty();
    }

22.leetcode 552. 学生出勤记录 II

552. 学生出勤记录 II

public int checkRecord(int n) {
       if(n == 0)return 0;
       if(n == 1)return 3;
       if(n == 2)return 8;
       int max = 1000000007;
       long[][][] dp = new long[n+1][2][3];
       dp[2][0][0] = 2; //两个数 且A的个数为0 结尾不为L  PP  LP
       dp[2][1][0] = 3; //两个数 且A的个数为1,皆为不为L  AP,LP,LA
       dp[2][0][1] = 1; //两个数 结尾为L 没有A  PL
       dp[2][1][1] = 1; //两个数 有A 结尾为L AL
       dp[2][0][2] = 1; //LL
       dp[2][1][2] = 0;
       for (int i = 3; i <= n; i++) {
           dp[i][0][0] = (dp[i - 1][0][0] + dp[i - 1][0][2] + dp[i - 1][0][1]) % max;
           dp[i][1][0] = (dp[i - 1][0][0] + dp[i - 1][0][1] + dp[i - 1][1][1] + dp[i -1][0][2] + dp[i - 1][1][2] + dp[i - 1][1][0]) % max;
           dp[i][0][1] = dp[i - 1][0][0] % max;
           dp[i][1][1] = dp[i - 1][1][0] % max;
           dp[i][0][2] = dp[i - 1][0][1] % max;
           dp[i][1][2] = dp[i - 1][1][1] % max;
       }
       return (int)((dp[n][0][0] + dp[n][1][0] + dp[n][0][1] + dp[n][0][2] + dp[n][1][1] + dp[n][1][2]) % max);
    }

23.三角形最小路径和

120. 三角形最小路径和I

public int minimumTotal(List<List<Integer>> triangle) {
        if (triangle == null || triangle.size() == 0) return 0;
        int[][] dp = new int[triangle.size() + 1][triangle.size() + 1];

        for (int i = triangle.size() - 1; i >= 0; i--) {
            List<Integer> cur = triangle.get(i);
            for (int j = 0; j < cur.size(); j++) {
                dp[i][j] = Math.min(dp[i + 1][j], dp[i + 1][j + 1])+ cur.get(j);
            }
        }
        return dp[0][0];
    }

24.最大子序和

53. 最大子序和

如果之前的subarray的总体和大于0的话,我们认为其对后续结果是由贡献的,选择加入之前的subarray
如果之间的subarray小于等于0的我们认为其对后面的结果是没有贡献的,这种情况下我们选择以当前的数字开始,重新起一个subarray


 public int maxSubArray(int[] nums) {
        int maxSum = nums[0];
        int curSum = nums[0];
        for (int i = 1; i < nums.length; i++) {
            //如果curSum 对当前值有增益的话就加上,没有的话就重启一个curSum
            curSum = Math.max(nums[i], curSum + nums[i]);
            maxSum = Math.max(curSum, maxSum);
        }
        return maxSum;}

25.最大连续子序列积

53. 最大连续子序列积

这题和最大连续子序和非常类似,只不过变成了,所以解决思路也很类似。
有个小细节需要注意,就是负负得正,两个负数的乘积是正数,因此我们不仅要跟踪最大值,也要跟踪最小值

public int maxProduct(int[] nums) {
        int n = nums.length;
        if (n == 0)return 0;
        int[] dpMax = new int[n];
        dpMax[0] = nums[0];
        int[] dpMin = new int[n];
        dpMin[0] = nums[0];
        int max = nums[0];
        
        for (int i = 1; i < n; i++) {
            dpMax[i] = Math.max(dpMax[i - 1] * nums[i], Math.max(dpMin[i - 1] * nums[i], nums[i]));
            dpMin[i] = Math.min(dpMin[i - 1] * nums[i], Math.min(dpMax[i - 1] * nums[i], nums[i]));
            max = Math.max(dpMax[i], max);
        }

        return max;
    }

26.最长上升子序列

300. 最长上升子序列

  • 子序列并不要求连续,子串一定是连续的
  • dp[i]表示以nums[i]结尾的最长上升子序列的长度,这里的nums[i]一定会被选取,且必须放在最后一个元素
  • 遍历到nums[i]的时候,考虑把索引i之前的所有的数都看一遍,只要当前的数nums[i]严格大于之前的某个数,那么nums[i]就可以接在这个数后面想成一个更长的子序列,dp[i]就等于索引i之前严格小于nums[i]的状态的最大者加上1.
  • dp[0] = 1
  • 所有dp[i]中的最大值
 public int lengthOfLIS(int[] nums) {
        int len = nums.length;
        if (len < 2) {
            return len;
        }
        int[] dp = new int[len];
        Arrays.fill(dp, 1);
        int max = 0;
        for (int i = 1; i < len; i++) {
            for (int j = 0; j < i ;j++) {
                if (nums[j] < nums[i]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
            max = Math.max(max, dp[i]);
        }
        return max;
    }

27.分割回文串 II

132. 分割回文串 II

动态规划(Java、Python

  • dp[i]表示以i结尾的字符串能分成回文串的最少分割数
  • 如果 s[0:i] 本身就是一个回文串,那么不用分割,即 dp[i] = 0
  • 即如果 s[0:i] 本身不是一个回文串,就尝试分割,枚举分割的边界 j
  • 如果 s[j + 1, i] 不是回文串,尝试下一个分割边界。
  • 如果 s[j + 1, i] 是回文串,则 dp[i] 就是在 dp[j] 的基础上多一个分割
  public int minCut(String s) {
        int len = s.length();
        if (len < 2) {
            return 0;
        }
        int[] dp = new int[len];//dp[i]表示以i结尾的字符串能分成回文串的最少分割数
        for (int i = 0; i < len; i++) {
            dp[i] = i;
        }

        for (int i = 1; i < len; i++) {
            if (isPalidrome(s, 0 , i)) {
                dp[i] = 0;
                continue;
            }
            //当 j == i的时候 判断的是一个字符,一定是回文,所以枚举到i - 1即可
            for (int j = 0; j <= i - 1; j++) {
                if (isPalidrome(s,j + 1,i)) {
                    dp[i] = Math.min(dp[j] + 1, dp[i]);
                }
            }
        }
        return dp[len - 1];
    }

    private boolean isPalidrome(String s, int left, int right) {
        while (left < right) {
            if (s.charAt(left) != s.charAt(right)) {
                return false;
            }
            left++;
            right--;
        }
        return true;
    }

使用动态规划保存状态来优化判断从i到j是否是回文串

public int minCut(String s) {
        int len = s.length();
        if (len < 2) {
            return 0;
        }
        int[] dp = new int[len];//dp[i]表示以i结尾的字符串能分成回文串的最少分割数
        for (int i = 0; i < len; i++) {
            dp[i] = i;
        }
        boolean[][] dict = isPalidrome(s);
        for (int i = 1; i < len; i++) {
            if (dict[0][i]) {
                dp[i] = 0;
                continue;
            }
            //当 j == i的时候 判断的是一个字符,一定是回文,所以枚举到i - 1即可
            for (int j = 0; j <= i - 1; j++) {
                if (dict[j + 1][i]) {
                    dp[i] = Math.min(dp[j] + 1, dp[i]);
                }
            }
        }
        return dp[len - 1];
    }

    private boolean[][] isPalidrome(String s) {
        int len = s.length();
        boolean[][] dp = new boolean[len][len];
        for (int right = 1; right < len; right++) {
            for (int left = 0; left <= right; left++) {
                if (s.charAt(left) == s.charAt(right) && (right - left <= 2 || dp[left + 1][right - 1])) {
                    dp[left][right] = true;
                }
            }
        }
        return dp;
    }

28. 交错字符串

97. 交错字符串

dp题解

dp[i][j] 表示s1[0,i) s2[0, j)能否组合构成s3[0, i + j);
这里不包括右边界,主要是为了考虑开始的时候如果只取s1,那么s2就是空串,这样的话dp[i][0]就能表示s2是空串

如果dp[i - 1][j] = true表示s1[0,i - 1) s2[0, j)能组合构成s3[0, i + j - 1),并且s1[i - 1] = s3[i + j - 1]那么dp[i][j] = true;
如果 dp [ i ] [ j - 1 ] == true,并且 s2 [ j - 1 ] == s3 [ i + j - 1], dp [ i ] [ j ] = true 。

否则的话,就更新为 dp [ i ] [ j ] = false。
如果 i 为 0,或者 j 为 0,那直接判断 s2 和 s3 对应的字母或者 s1 和 s3 对应的字母即可。

 public boolean isInterleave(String s1, String s2, String s3) {
        if (s1.length() + s2.length() != s3.length()) {
            return false;
        }
        //dp[i][j] 表示s1[0,i) s2[0, j)组合构成s3[0, i + j);
        boolean[][] dp = new boolean[s1.length() + 1][s2.length() + 1];
        for (int i = 0; i <= s1.length(); i++) {
            for (int j = 0; j <= s2.length(); j++) {
                if (i == 0 && j == 0) {
                    dp[i][j] = true;
                }else if (i == 0) {
                    dp[i][j] = dp[i][j - 1] && s2.charAt(j - 1) == s3.charAt(j - 1);
                }else if (j == 0) {
                    dp[i][j] = dp[i - 1][j] && s1.charAt(i - 1) == s3.charAt(i - 1);
                }else {
                    dp[i][j] = dp[i - 1][j] && s1.charAt(i - 1) == s3.charAt(i - 1 + j) ||
                    dp[i][j - 1] && s2.charAt(j - 1) == s3.charAt(i + j - 1);
                }          
            }
        }
        return dp[s1.length()][s2.length()];
    }

29. 单词拆分

139. 单词拆分

//dp[i]表示s[0,i)能否由wordDict构成
    public boolean wordBreak(String s, List<String> wordDict) {
       boolean[] dp = new boolean[s.length() + 1];
       dp[0] = true;
       for (int i = 1; i <= s.length(); i++) {
           for (int j = 0; j < i; j++) {
               if (dp[j] && wordDict.contains(s.substring(j, i))){
                   dp[i] = true;
                   break;
               }
           } 
       }
       return dp[s.length()];
    }

30. 单词拆分 II

140. 单词拆分 II

直接用递归的方法,先判断当前字符串在不在worddict中,如果在的话就递归的去求解剩余字符串的所有可能。

public List<String> wordBreak(String s, List<String> wordDict) {
        HashSet<String> set = new HashSet<>();
        for (int i = 0; i < wordDict.size(); i++) {
            set.add(wordDict.get(i));
        }
        return wordBreakHelper(s, set, new HashMap<String, List<String>>());
    }

    private List<String> wordBreakHelper(String s, HashSet<String> set, HashMap<String, List<String>> map) {
        if (s.length() == 0) {
                return new ArrayList<>();
            }
            if (map.containsKey(s)) {
                return map.get(s);
            }
            List<String> res = new ArrayList<>();
            for (int j = 0; j < s.length(); j++) {
                //判断当前字符串是否存在
                if (set.contains(s.substring(j, s.length()))) {
                    //空串的情况,直接加入
                    if (j == 0) {
                        res.add(s.substring(j, s.length()));
                    } else {
                        //递归得到剩余字符串的所有组成可能,然后和当前字符串分别用空格连起来加到结果中
                        List<String> temp = wordBreakHelper(s.substring(0, j), set, map);
                        for (int k = 0; k < temp.size(); k++) {
                            String t = temp.get(k);
                            res.add(t + " " + s.substring(j, s.length()));
                        }
                    }

                }
            }
            //缓存结果
            map.put(s, res);
            return res;
    }

31. 不同的子序列

115. 不同的子序列

这里我们用一个二维数组 dp[m][n] 对应于从 S[m,S_len) 中能选出多少个 T[n,T_len)。
当 m == S_len,意味着S是空串,此时dp[S_len][n],n 取 0 到 T_len - 1的值都为 0。
当 n == T_len,意味着T是空串,此时dp[m][T_len],m 取 0 到 S_len的值都为 1。
然后状态转移的话和解法一分析的一样。如果求dp[s][t]。
S[s] == T[t],当前字符相等,那就对应两种情况,选择S的当前字母和不选择S的当前字母
dp[s][t] = dp[s+1][t+1] + dp[s+1][t]
S[s] != T[t],只有一种情况,不选择S的当前字母
dp[s][t] = dp[s+1][t]

public int numDistinct(String s, String t) {
        int m = s.length();
        int n = t.length();
        int[][] dp = new int[m +  1][n + 1];
        for (int i = 0; i <= m; i++){
            dp[i][n] = 1;
        }
        for (int i = m - 1; i >= 0; i--) {
            for (int j = n - 1; j >= 0; j--) {
                if (s.charAt(i) == t.charAt(j)) {
                   dp[i][j] = dp[i + 1][j + 1] + dp[i + 1][j];
                }else {
                    dp[i][j] = dp[i + 1][j];
                }
            }
        }
        return dp[0][0];
    }

32. 二维区域和检索 - 矩阵不可变

304. 二维区域和检索 - 矩阵不可变

private int[][] dp;
    public NumMatrix(int[][] matrix) {
        if (matrix.length == 0 || matrix[0].length == 0) return;
        dp = new int[matrix.length + 1][matrix[0].length + 1];
        for (int r = 0; r < matrix.length; r++) {
            for (int c = 0; c < matrix[0].length; c++) {
                dp[r + 1][c + 1] = dp[r + 1][c] + dp[r][c + 1] + matrix[r][c] - dp[r][c];
            }
        }
    }
    
    public int sumRegion(int row1, int col1, int row2, int col2) {
        return dp[row2 + 1][col2 + 1] + dp[row1][col1] - dp[row1][col2 + 1] - dp[row2 + 1][col1];
    }

你可能感兴趣的:(数据结构与算法)