leetcode高頻題筆記之動態規劃

最難啃的骨頭開始啃了

斐波拉契數組變形系列

70.爬樓梯

在這裏插入圖片描述
dp方程:

dp[i] = dp[i-1] + dp[i-2]

自底向上迭代

class Solution {
    public int climbStairs(int n) {
        if (n == 0) return 1;
        if (n < 3) return n;
        int dp[] = new int[n + 1];
        dp[0] = 1;
        dp[1] = 1;
        dp[2] = 2;
        for (int i = 3; i <= n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
}

自頂向下記憶化遞歸:

class Solution {
    private int[] cache;

    public int climbStairs(int n) {
        if (n == 0) return 1;
        cache = new int[n + 1];
        cache[0] = 1;
        return helper(n);
    }

    private int helper(int n) {
        if (n < 3) {
            if (cache[n] == 0) {
                cache[n] = n;
            }
        }
        if (cache[n] == 0) {
            cache[n] = helper(n - 1) + helper(n - 2);
        }
        return cache[n];
    }
}

322.零錢兌換

在這裏插入圖片描述
思路類似於爬樓梯和斐波拉契數列
f(n)問題可以轉換爲求f(n-coin[i])的最小值+1的問題

自頂向下,帶緩存的遞歸

class Solution {
   static int cache[];

    public int coinChange(int[] coins, int amount) {
        if (amount < 1) return 0;
        cache = new int[amount + 1];
        return helper(coins, amount);

    }

    private int helper(int[] coins, int amount) {

        if (amount == 0) return 0;
        if (amount < 0) return -1;
        if (cache[amount] == 0) {
            int curmin = Integer.MAX_VALUE;
            for (int i = 0; i < coins.length; i++) {
                int res = helper(coins, amount - coins[i]);
                if (res >= 0 && curmin > res)
                    curmin = res+1;
            }
            cache[amount] = (curmin == Integer.MAX_VALUE) ? -1 : curmin;
        }
        return cache[amount];
    }

}

自底向上,dp迭代
因爲求最小值,所以先初始化爲比最大還大,如果迭代完了小於等於最大值,那麼就找到了,如果依然大於最大值,說明沒找到

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 (coin <= i) {
                    dp[i] = Math.min(dp[i], dp[i - coin] + 1);
                }
            }
        }
        return dp[amount] > amount ? -1 : dp[amount];
    }
}

不同路徑系列

62.不同路徑

在這裏插入圖片描述
子問題:右側點的路徑數+下側點的路徑數
得到dp方程:dp[i][j] = dp[i+1][j]+dp[i][j+1]
我們求的是(0,0)點的可能到達的路徑數,所以從最後開始遍歷,推出(0,0)的總路徑數
最後一行和最後一列比較固定,按這個走法只能有一條路能到達,全部初始化爲1

class Solution {
     //dp方程
    //opt[i][j] = opt[i+1][j]+opt[i][j+1]
    public int uniquePaths(int m, int n) {
        int dp[][] = new int[m][n];

        for (int k = 0; k < m; k++) {
            dp[k][n - 1] = 1;
        }
        for (int k = 0; k < n; k++) {
            dp[m - 1][k] = 1;
        }
        for (int k = m - 2; k >= 0; k--) {
            for (int l = n - 2; l >= 0; l--) {
                dp[k][l] = dp[k+1][l]+dp[k][l+1];
            }
        }
        return dp[0][0];
    }
}

優化:將dp數組簡化成一維的
每次迭代只需要暫存最後一排的就夠了,暫存數組多開一維,保證最後一列沒有初始化也可以使用

class Solution {
     public int uniquePaths(int m, int n) {
        //定義一維的暫存空間就夠了,從最後一排開始存儲
        int dp[] = new int[n + 1];
        //初始化最後一排,數組大小爲n-1,最後一個保持爲0,避免迭代時數組越界
        for (int k = 0; k < n; k++) {
            dp[k] = 1;
        }
        for (int k = m - 2; k >= 0; k--) {
            for (int l = n - 1; l >= 0; l--) {
                dp[l] = dp[l]+dp[l+1];
            }
        }
        return dp[0];
    }
}

63.不同路徑II

在這裏插入圖片描述
相比上一個題,增加了障礙物
首先還是初始化dp數組的最後一行和最後一列,然後最後往前遞推,遇到障礙物就跳過
初始化發生變化,不再是全賦1,而是從最後一個開始推:如果遇到障礙物就跳過沒如果沒有障礙物當前值就等於後一個值,一旦遇到了障礙物跳過了,障礙物位置爲0,之前的都爲0
遍歷時,如果遇到障礙物就跳過,障礙物位置就爲0

class Solution {
    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        if (obstacleGrid == null || obstacleGrid.length == 0) return 0;
        int m = obstacleGrid.length;
        int n = obstacleGrid[0].length;
        int dp[][] = new int[m][n];
        if (obstacleGrid[m - 1][n - 1] == 0) dp[m - 1][n - 1] = 1;
        //從最後一個點開始,如果不是石頭就和前一個一樣,如果是石頭就初始化爲0
        for (int k = m - 2; k >= 0; k--) {
            if (obstacleGrid[k][n - 1] == 0) dp[k][n - 1] = dp[k + 1][n - 1];
        }
        for (int k = n - 2; k >= 0; k--) {
            if (obstacleGrid[m - 1][k] == 0) dp[m - 1][k] = dp[m - 1][k + 1];
        }

        for (int k = m - 2; k >= 0; k--) {
            for (int l = n - 2; l >= 0; l--) {
                if (obstacleGrid[k][l] == 0) dp[k][l] = dp[k + 1][l] + dp[k][l + 1];
            }
        }
        return dp[0][0];
    }
}

優化:將dp數組簡化爲一維的

思路和上方一致,有一點需要小心,使用二維時如果遇到障礙物可以直接跳過,因爲初始化爲0,而使用一維時不行,必須對障礙物進行處理,將障礙物出手動置爲1

class Solution {
    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        if (obstacleGrid == null || obstacleGrid.length == 0) return 0;
        int m = obstacleGrid.length;
        int n = obstacleGrid[0].length;
        int dp[] = new int[n + 1];
        if (obstacleGrid[m - 1][n - 1] == 0) dp[n - 1] = 1;
        
        for (int k = n - 2; k >= 0; k--) {
            if (obstacleGrid[m - 1][k] == 0) dp[k] = dp[k + 1];
        }

        for (int k = m - 2; k >= 0; k--) {
            for (int l = n - 1; l >= 0; l--) {
                if (obstacleGrid[k][l] == 0) dp[l] = dp[l] + dp[l + 1];
                else dp[l] = 0;
            }
        }
        return dp[0];
    }
}

120.三角形最小路徑和

在這裏插入圖片描述
和不同路徑類似,只是只跑了一半的圖,如果不求最小路徑而是求路徑條數就和不同路徑基本一致了

將集合格式改變一下就比較清楚了

  [2],
  [3,4],
  [6,5,7],
  [4,1,8,3]

從倒數第二層開始,找到下一層下面的數和右斜下的數中的小的一個,加上當前位置,就是局部最小了
dp方程:dp[j] = Math.min(dp[j], dp[j + 1]) + num[i][j]

dp初始化爲最後一行的下一行,是一個空白行
然後依次向上迭代,最後只剩下第一行第一個就是最小路徑的值

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

不開闢新數組,複用原集合

class Solution {
    public int minimumTotal(List<List<Integer>> triangle) {

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

自頂向下記憶化遞歸

class Solution {
    Integer[][] cache;

    public int minimumTotal(List<List<Integer>> triangle) {
        cache = new Integer[triangle.size()][triangle.size()];
        return helper(0, 0, triangle);
    }

    private int helper(int level, int c, List<List<Integer>> triangle) {
        if (cache[level][c] != null) {
            return cache[level][c];
        }
        if (level == triangle.size() - 1) {
            return cache[level][c] = triangle.get(level).get(c);
        }
        int left = helper(level + 1, c, triangle);
        int right = helper(level + 1, c + 1, triangle);
        return cache[level][c] = Math.min(left, right) + triangle.get(level).get(c);
    }
}

64.最小路徑和

在這裏插入圖片描述
二維的dp

class Solution {
    public int minPathSum(int[][] grid) {
        if (grid == null || grid.length == 0 || grid[0].length == 0) return 0;
        int m = grid.length;
        int n = grid[0].length;
        int dp[][] = new int[m][n];
        dp[0][0] = grid[0][0];
        //初始化dp的第一行和第一列
        for (int i = 1; i < m; i++) {
            dp[i][0] += grid[i][0] + dp[i - 1][0];
        }
        for (int i = 1; i < n; i++) {
            dp[0][i] = grid[0][i] + dp[0][i - 1];
        }
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
            }
        }
        return dp[m - 1][n - 1];
    }
}

壓縮dp數組到一維
dp[0]設置成Integer.MAX_VALUE;從dp[1]開始使用

class Solution {
    public int minPathSum(int[][] grid) {
        if (grid == null || grid.length == 0 || grid[0].length == 0) return 0;
        int m = grid.length;
        int n = grid[0].length;
        int dp[] = new int[n + 1];
        dp[0] = Integer.MAX_VALUE;
        dp[1] = grid[0][0];
        //初始化dp的第一行和第一列
        for (int i = 1; i < n; i++) {
            dp[i + 1] += grid[0][i] + dp[i];
        }
        for (int i = 1; i < m; i++) {

            for (int j = 0; j < n; j++) {
                dp[j + 1] = grid[i][j] + Math.min(dp[j], dp[j + 1]);
            }

        }
        return dp[n];
    }
}

子序列系列

53.最大子序和(子序列最大和)

在這裏插入圖片描述
dp方程:dp[i] += dp[i-1]
如果前一個值大於零,說明加上它當前值會增大,就加上,如果小於等於零,就不加
每一個位置都求一下最大和,遍歷完成獲得全局最大和

class Solution {
     public int maxSubArray(int[] nums) {
        int resMax = nums[0];
        for (int i = 1; i < nums.length; i++) {
            if (nums[i - 1] > 0) nums[i] += nums[i - 1];
            resMax = Math.max(resMax, nums[i]);
        }
        return resMax;
    }
}

152.乘積最大子數組(子序列最大積)

在這裏插入圖片描述
維護兩個變量,當前最大值和當前最小值, 如果當前值爲負數,就交換最小值和最大值

class Solution {
    public int maxProduct(int[] nums) {
        int max = Integer.MIN_VALUE;
        int curmax = 1;
        int curmin = 1;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] < 0) {
                int tmp = curmax;
                curmax = curmin;
                curmin = tmp;
            }
            curmax = Math.max(nums[i], curmax * nums[i]);
            curmin = Math.min(nums[i], curmin * nums[i]);
            max = Math.max(max, curmax);
        }
        return max;
    }
}

解法二:兩次遍歷
將數組按0分成n個子數組,求不含0的子數組的最大積
正序和逆序分別遍歷一次,兩個全局最大積的較大值就是最大積

class Solution {
    public int maxProduct(int[] nums) {
        int max = 1;
        int resmax = Integer.MIN_VALUE;

        for (int i = 0; i < nums.length; i++) {
            max *= nums[i];
            resmax = Math.max(resmax, max);
            if (max == 0) max = 1;
        }
        max = 1;
        for (int i = nums.length - 1; i >= 0; i--) {
            max *= nums[i];
            resmax = Math.max(resmax, max);
            if (max == 0) max = 1;
        }
        return resmax;
    }
}

1143.最長公共子序列(子序列最大公共序列)

在這裏插入圖片描述
將一維變成二維操作
在這裏插入圖片描述
自頂向下:從最後開始看,如果最後兩個字符一樣的,結果等於各自減去最後一個後的長度+1,如果最後兩個字符不相同,等於任選一個減去最後一個字符後的計算長度中大的那一個
自底向上:從第一個字符開始,數組從1下標開始,如果兩個字符相同,則長度等於各自減一的後計算長度+1,如果不相同,則任選一個減去最後一個字符後的計算長度中大的那一個。自底向上迭代,最後位置就是最終答案

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        if (text1 == null || text2 == null) return 0;
        int m = text1.length();
        int n = text2.length();
        int dp[][] = new int[m + 1][n + 1];
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
                    dp[i][j] = 1 + dp[i - 1][j - 1];
                } else {
                    dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]);
                }
            }
        }
        return dp[m][n];
    }
}

打家劫舍系列

198.打家劫舍

在這裏插入圖片描述
新加一個維度,偷還是不偷
子問題:

  • 如果這一家偷,那麼收入等於前一家不偷+這一家偷的 dp[i][1] = nums[i] + dp[i - 1][0];
  • 如果這一家不偷,那麼收入等於前一家偷或者不偷的較大值dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1]);
class Solution {
    public int rob(int[] nums) {
        if (nums == null || nums.length == 0) return 0;
        int n = nums.length;
        int[][] dp = new int[n][2];

        dp[0][0] = 0;
        dp[0][1] = nums[0];
        for (int i = 1; i < n; i++) {
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1]);
            dp[i][1] = nums[i] + dp[i - 1][0];
        }
        return Math.max(dp[n - 1][0], dp[n - 1][1]);
    }
}

不加維度,問題轉換爲求dp[i-2]+num[i]和dp[i-1]的較大值的問題
dp方程轉換爲:dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);

class Solution {
    public int rob(int[] nums) {
        if (nums.length == 0) return 0;
        if (nums.length == 1) return nums[0];
        if (nums.length == 2) return Math.max(nums[0], nums[1]);
        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];
    }
}

爲了避免判斷長度爲0 1 2,採用length+2長度的dp數組

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

213.打家劫舍II

在這裏插入圖片描述
與打家劫舍I相比變成了環了,意味着還要考慮如果偷了第一家最後一家不能偷,如果偷了最後一家第一家不能偷

那麼問題可以簡化成:從第一家到倒數第二家遍歷一次從第二家到最後一家遍歷一次。這樣就破除了環了,然後取較大值返回

在第一個題的基礎上進行改造:

一維:

class Solution {
    public int rob(int[] nums) {

        if (nums.length == 0) return 0;
        if (nums.length == 1) return nums[0];
        if (nums.length == 2) return Math.max(nums[0], nums[1]);
        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 - 1; i++) {
            dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
        }
        dp[0] = dp[nums.length-2];//暫存第一個遍歷結果
        dp[1] = nums[1];
        dp[2] = Math.max(nums[1], nums[2]);
        for (int i = 3; i < nums.length; i++) {
            dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
        }
        return Math.max(dp[nums.length - 1], dp[0]);
    }
}

簡化:

class Solution {
    public int rob(int[] nums) {
        if (nums.length == 0) return 0;
        if (nums.length == 1) return nums[0];
        int dp[] = new int[nums.length + 2];

        for (int i = 0; i < nums.length - 1; i++) {
            dp[i + 2] = Math.max(dp[i + 1], dp[i] + nums[i]);
        }
        int res1 = dp[nums.length];//暫存第一個遍歷結果
        Arrays.fill(dp, 0);
        for (int i = 1; i < nums.length; i++) {
            dp[i + 2] = Math.max(dp[i + 1], dp[i] + nums[i]);
        }
        return Math.max(dp[nums.length + 1], res1);
    }
}

二維:

class Solution {
    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][2];

        dp[0][0] = 0;
        dp[0][1] = nums[0];

        for (int i = 1; i < nums.length - 1; i++) {
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1]);
            dp[i][1] = nums[i] + dp[i - 1][0];
        }
        int res1 = Math.max(dp[nums.length - 2][0], dp[nums.length - 2][1]);

        dp[1][0] = 0;
        dp[1][1] = nums[1];
        for (int i = 2; i < nums.length; i++) {
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1]);
            dp[i][1] = nums[i] + dp[i - 1][0];
        }
        int res2 = Math.max(dp[nums.length - 1][0], dp[nums.length - 1][1]);
        
        return Math.max(res1, res2);
    }
}

337.打家劫舍III

在這裏插入圖片描述
自頂向下傻遞歸

class Solution {
    public int rob(TreeNode root) {
        if (root == null) return 0;
        //當前層和下下層的和
        int val1 = root.val;
        if (root.left!=null)val1+=rob(root.left.left)+rob(root.left.right);
        if (root.right!=null)val1+=rob(root.right.left)+rob(root.right.right);
        //下一層的和
        int val2  = rob(root.left)+rob(root.right);
        return Math.max(val1,val2);
    }
}

自頂向下帶緩存的遞歸

class Solution {
    Map<TreeNode, Integer> cache;

    public int rob(TreeNode root) {
        cache = new HashMap<TreeNode, Integer>();
        return helper(root);
    }

    private int helper(TreeNode root) {
        if (root == null) return 0;
        if (cache.containsKey(root)) return cache.get(root);
        //當前層和下下層的和
        int val1 = root.val;
        if (root.left != null) val1 += helper(root.left.left) + helper(root.left.right);
        if (root.right != null) val1 += helper(root.right.left) + helper(root.right.right);
        //下一層的和
        int val2 = helper(root.left) + helper(root.right);
        cache.put(root, Math.max(val1, val2));
        return cache.get(root);
    }
}

自底向上後序遍歷加升維
和前面兩個打家劫舍一樣,可以升一個維度,用偷或者不偷來描述一次操作
採用後序遍歷保證自底向上逐層搜索

class Solution {
    public int rob(TreeNode root) {
        int[] helper = helper(root);
        return Math.max(helper[0], helper[1]);
    }

    //返回結果是偷或者不偷的值
    private int[] helper(TreeNode root) {
        if (root == null) return new int[]{0, 0};

        int[] left = helper(root.left);
        int[] right = helper(root.right);
        //選擇偷當前節點,值爲當前節點+下一層不偷
        int choose = root.val + left[1] + right[1];
        //選擇不偷當前節點,值爲下一層偷的最大值的和
        int noChoose = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
        return new int[]{choose, noChoose};
    }
}

買賣股票系列

121.買賣股票的最佳時機

在這裏插入圖片描述
動態規劃:
因爲最多交易一次,狀態一共只有三種

  • 一次都不交易
  • 買了一次
  • 買了一次 賣了一次
class Solution {
    public int maxProfit(int[] prices) {
        if (prices.length < 2) return 0;
        int dp[][] = new int[prices.length][3];

        dp[0][0] = 0;//不操作
        dp[0][1] = -prices[0];//買入
        dp[0][2] = Integer.MIN_VALUE;//賣出,不可能存在

        for (int i = 1; i < prices.length; i++) {
        	//可能是當前位置操作的,可也能上一次就操作了
            dp[i][1] = Math.max(dp[i - 1][0] - prices[i], dp[i - 1][1]);
            dp[i][2] = Math.max(dp[i - 1][1] + prices[i], dp[i - 1][2]);
        }
        return Math.max(0, dp[prices.length - 1][2]);
    }
}

這個題動態規劃就比較晦澀,效率不高,最好還是用貪心來

一次遍歷,如果小於最小值更新最小值,如果大於最小值假設賣出得收益,更新最大收益

class Solution {
    public int maxProfit(int[] prices) {
        if (prices.length < 2) return 0;

        int min = Integer.MAX_VALUE;
        int resMax = 0;
        for (int i = 0; i < prices.length; i++) {
            if (prices[i] < min) {
                min = prices[i];
            } else {
                resMax = Math.max(resMax, prices[i] - min);
            }
        }
        return resMax;
    }
}

122.買賣股票的最佳時機II

在這裏插入圖片描述
動態規劃
因爲可以無限次買賣,定義兩種狀態。持有和賣出

class Solution {
    public int maxProfit(int[] prices) {
        if (prices.length < 2) return 0;
        int dp[][] = new int[prices.length][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][1] + prices[i], dp[i - 1][0]);
            //當前持有,可能上次沒有這次買入的,可能上次就持有這次不操作
            dp[i][1] = Math.max(dp[i - 1][0] - prices[i], dp[i - 1][1]);
        }
        return dp[prices.length - 1][0];
    }
}

這個題動態規劃就比較晦澀,效率不高,最好還是用貪心來

一次遍歷,如果比前一天價格高就前一天買當天賣

class Solution {
    public int maxProfit(int[] prices) {
        if (prices.length < 2) return 0;
        int res = 0;
        for (int i = 1; i < prices.length; i++) {
            if (prices[i] - prices[i - 1] > 0) {
                res += prices[i] - prices[i - 1];
            }
        }
        return res;
    }
}

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

在這裏插入圖片描述
因爲最多交易兩次,狀態一共只有五種

  • 一次都不交易
  • 買了一次
  • 買了一次 賣了一次
  • 買了一次 賣了一次 買了一次
  • 買了一次 賣了一次 賣了一次 賣了一次

針對這五種狀態進行規劃。

class Solution {
     public int maxProfit(int[] prices) {
        if (prices.length < 2) return 0;

        //二個維度:第幾天,買賣狀態
        int dp[][] = new int[prices.length][5];
        /**
         *         j = 0:還未開始交易;
         *         j = 1:第 1 次買入一支股票;
         *         j = 2:第 1 次賣出一支股票;
         *         j = 3:第 2 次買入一支股票;
         *         j = 4:第 2 次賣出一支股票。
         */
        dp[0][0] = 0;
        //第一次買入
        dp[0][1] = -prices[0];
        //第一次買入兩個,不可能
        dp[0][3] = Integer.MIN_VALUE;

        for (int i = 1; i < prices.length; i++) {
        	//可能是當前位置操作的,可也能上一次就操作了
            dp[i][1] = Math.max(dp[i - 1][0] - prices[i], dp[i - 1][1]);
            dp[i][2] = Math.max(dp[i - 1][1] + prices[i], dp[i - 1][2]);
            dp[i][3] = Math.max(dp[i - 1][2] - prices[i], dp[i - 1][3]);
            dp[i][4] = Math.max(dp[i - 1][3] + prices[i], dp[i - 1][4]);
        }
        return Math.max(0, Math.max(dp[prices.length - 1][2], dp[prices.length - 1][4]));
    }

}

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

在這裏插入圖片描述
當k是一個變量時,我們簡單的使用二維枚舉狀態就不夠用了,所以需要在問題1和問題3的基礎上升級到三維
三個維度:

  • 第i天
  • 第j次交易
  • 是否持有股票

如果只是簡單的把問題1和問題3的代碼拿去升維然後循環弄,會發現超時過不了

再次觀察,如果k大於天數的一半,說明最大次數滿足無限買賣,問題退化成問題2

class Solution {
    public int maxProfit(int k, int[] prices) {
        if (prices.length < 2 || k < 1) return 0;
        
        //k比prices.length的一半大,說明可以轉化爲問題2,無限次交易
        if (k > prices.length >> 1) {
            return maxProfit(prices);
        }

        //三個維度:第幾天、交易次數、是否持有股票
        int dp[][][] = new int[prices.length][k + 1][2];

        //初始化第一天
        for (int i = 1; i < k + 1; i++) {
            dp[0][i][1] = -prices[0];
        }

        for (int i = 1; i < prices.length; i++) {
            for (int j = 1; j <= k; j++) {
                dp[i][j][0] = Math.max(dp[i - 1][j][1] + prices[i], dp[i - 1][j][0]);
                dp[i][j][1] = Math.max(dp[i - 1][j - 1][0] - prices[i], dp[i - 1][j][1]);
            }
        }
        return dp[prices.length - 1][k][0];
    }
    //貪心實現無限次交易
    public int maxProfit(int[] prices) {
        if (prices.length < 2) return 0;
        int res = 0;
        for (int i = 1; i < prices.length; i++) {
            if (prices[i] - prices[i - 1] > 0) {
                res += prices[i] - prices[i - 1];
            }
        }
        return res;
    }
}

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

在這裏插入圖片描述
如果沒有凍結期,那就是無限買賣問題,類似於問題2,所以在問題2的持有和不持有狀態上再加一個凍結期狀態

class Solution {
    public int maxProfit(int[] prices) {
        if (prices.length < 2) return 0;
        int dp[][] = new int[prices.length][3];

        //初始化第一天
        dp[0][0] = 0;//不持股
        dp[0][1] = -prices[0];//持股
        dp[0][0] = 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][2] - prices[i]);
            //今天空窗期,說明昨天不持股
            dp[i][2] = dp[i - 1][0];
        }
        return dp[prices.length - 1][0];
    }
}

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

在這裏插入圖片描述
如果沒有手續費,那就是無限買賣問題,和問題2一樣,加上手續費後,貪心就不行了,要交易的次數儘量少利潤儘量多

採用動態規劃只需要在每次買入或者賣出時扣一次手續費就可以了
代碼和思路同問題2基本一致

class Solution {
    public int maxProfit(int[] prices, int fee) {
        if (prices.length < 2) return 0;
        int dp[][] = new int[prices.length][2];

        //初始化第一天
        dp[0][0] = 0;//不持有
        dp[0][1] = -prices[0] - fee;//持有(一次交易扣一次手續費,所以統一在買入時扣)

        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] - fee);
        }
        return dp[prices.length - 1][0];
    }
}

尚未分類

32.最長有效括號

在這裏插入圖片描述
很容易想到暴力法,O(N*2),對暴力進行剪枝,代碼如下:
從第一個開始枚舉起始,內存循環枚舉結尾
如果左括號+1,如果右括號-1
如果計數器小於1說明除成對括號外出現了右括號前沒有左括號,退出這一層遍歷
如果計數器值爲0,更新最大成對的數目

class Solution {
    public int longestValidParentheses(String s) {
        int max = 0;
        int count = 0;
        for (int i = 0; i < s.length()-1; i++) {
            count = 0;
            for (int j = i; j < s.length(); j++) {
                if (s.charAt(j) == '(') count++;
                else count--;
                if (count < 0) break;
                if (count == 0) max = Math.max(max, j - i + 1);
            }
        }
        return max;
    }
}

暴力的時間和空間複雜度都比較高,採用動態規劃進行解決:
方法過於巧妙,好不容易理解透徹記錄下
在這裏插入圖片描述

一次遍歷字符串,如果是(就跳過,如果是)就進行操作

  • 如果)的前一個字符是(,剛好組成一對,dp[i-2]+2
    舉例:index = 3時,dp[i-2] = 2,index = 2index = 3組成一對,dp[3] = dp[1]+2 = 4

  • 如果)的前一個字符不是(,則查找除掉當前成對的字符後的前一個字符是否是(,如果不是就放棄了,當前置爲0.如果是dp[i]的值至少爲dp[i-1[+2,如果新匹配的字符的前一個字符(i - dp[i - 1] -2)屬於上一個符合的子序列,那麼兩個子序列就可以合併成一個子序列,長度再加上前面的
    舉例:index = 7時,dp[i-1]所屬的子序列爲index5到index6,除去子序列的前一個是index = 4,滿足條件,所以dp[7]至少爲dp[i-1]+2,然後判斷index = 5的前一個index = 4,發現index = 4這個下標存在,就加上dp[4]的值,得到dp[7] = dp]6]+2+dp[4]

class Solution {
    public int longestValidParentheses(String s) {
        int max = 0;
        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] = 2 + (i >= 2 ? dp[i - 2] : 0);
                    //如果去除掉當前有效段之後前的一個字符爲(,說明前後能再組成一對
                } else if (i - dp[i - 1] > 0 && s.charAt(i - dp[i - 1] - 1) == '(') {
                    //dp[i]的長度至少等於dp[i-1]+2,如果新插入的那個符號前一位還有符號就加入那個位置的dp值(如果那個位置屬於前一個段就加上了前一段的長度,如果是單獨的就相當於加0
                    dp[i] = dp[i - 1] + 2 + (i - dp[i - 1] >= 2 ? dp[i - dp[i - 1]- 2] : 0);
                }
                max = Math.max(max, dp[i]);
            }
        }
        return max;
    }
}

343.整數拆分

在這裏插入圖片描述
初始化dp數組爲1
dp[i]表示i可以拆分後的最大乘積
dp[i]的值是dp[i]、拆分爲兩個數乘積最大值,拆分爲兩個數其中一個數被拆分的最大積乘另一個數的積

class Solution {
    public int integerBreak(int n) {
        int dp[] = new int[n + 1];
        Arrays.fill(dp, 1);
        for (int i = 3; i <= n; i++) {
            for (int j = 1; j < i; j++) {
                dp[i] = Math.max(dp[i], Math.max(j * (i - j), dp[i - j] * j));
            }
        }
        return dp[n];
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章