背包
416.分割等和子集
动态规划 - 状态转移方程
class Solution {public boolean canPartition(int[] nums) {if(nums.length < 2) {return false;}int sum = 0, max = 0;for(int n : nums) {sum += n;max = Math.max(n, max);}if((sum & 1) == 1) {return false;}int target = sum / 2;if(max > target) {return false;}// 状态转移表boolean[][] status = new boolean[nums.length][target + 1];for(int i = 0; i < nums.length; i++) {status[i][0] = true;}status[0][nums[0]] = true;// status 放置的是每个阶段 [0, target] 区间有无到达过for(int i = 1; i < nums.length; i++) {int num = nums[i];for(int j = 1; j <= target; j++) {if(j >= num) {// 核心点在这status[i][j] = status[i - 1][j] || status[i - 1][j - num];} else {status[i][j] = status[i - 1][j];}}}return status[nums.length - 1][target];}}
动态规划-状态转移方程-一维数组
class Solution {public boolean canPartition(int[] nums) {int n = nums.length;if(n < 2) {return false;}int sum = 0, max = 0;for(int num : nums) {sum += num;max = Math.max(num, max);}if((sum & 1) == 1) {return false;}int target = sum / 2;if(max > target) {return false;}boolean[] dp = new boolean[target + 1];dp[0] = true;for(int i = 1; i < n; i++) {int num = nums[i];for(int j = target; j >= num; j--) {dp[j] |= dp[j - num];}}return dp[target];}}
494.目标和
回溯
class Solution {private int count = 0;public int findTargetSumWays(int[] nums, int target) {backtrace(nums, 0, target, 0);return count;}private void backtrace(int[] nums, int stage, int target, int sum) {if(stage == nums.length) {if(sum == target) {count++;}return;}backtrace(nums, stage + 1, target, sum + nums[stage]);backtrace(nums, stage + 1, target, sum - nums[stage]);}}
动态规划-状态转移方程-solution0
class Solution {public int findTargetSumWays(int[] nums, int target) {int n = nums.length;int sum = 0;for(int i = 0; i < n; i++) {sum += nums[i];}if(sum < Math.abs(target)) {return 0;}// 状态数组,dp[i][j],表示从数组nums中 0 - i 的元素进行加减可以得到 j 的方法数量。// 这里不能压缩成一维数组,因为当前阶段的状态dp[i][j],需要根据 dp[i][j - nums[i]] 和 dp[i][j + nums[i]] 来决定的// 压缩到一维数组后,如果从前往后遍历 dp[i][j - nums[i]] 则有可能不是当前阶段状态, 如果从后往前遍历,dp[i][j + nums[i]] 则有可能不是当阶段的状态int m = sum * 2 + 1;int[][] dp = new int[n][m];// 状态转移方程// dp[i][j] = dp[i - 1][j - nums[i]] + dp[i - 1][j + nums[i]]// 初始化// 当 nums[0] == 0 时,dp[0][sum - 0] = 1, dp[0][sum + 0] = 1,即dp[0][sum] = 2,这里的sum表示nums中执行全部执行加/减能达到的数,画出二维表格,sum 也是中间点,即代表 0 的位置if(nums[0] == 0) {dp[0][sum] = 2;} else {dp[0][sum - nums[0]] = 1;dp[0][sum + nums[0]] = 1;}for(int i = 1; i < n; i++) {for(int j = 0; j < m; j++) {if(j - nums[i] >= 0) {dp[i][j] = dp[i - 1][j - nums[i]];}if(j + nums[i] < m) {dp[i][j] += dp[i - 1][j + nums[i]];}}}return dp[n - 1][sum + target];}}
动态规划-状态转移方程-solution1
class Solution {public int findTargetSumWays(int[] nums, int target) {// 记数组之和为 sum,添加符号为 - 的元素之和为 neg,则其余添加符号为 + 的元素之和为 sum - beg// 那么可推导出 (sum - neg) - neg = sum - 2 * neg = target => neg = (sum - target) / 2// 由于数组 nums 中的元素都是非负整数,neg 也必须是非负整数,所以上式成立的前提是 sum - target 是非负偶数。若不符合该条件可直接返回 0。int n = nums.length;int sum = 0;for(int i = 0; i < nums.length; i++) {sum += nums[i];}int diff = sum - target;if(diff < 0 || diff % 2 != 0) {return 0;}int neg = diff / 2;// 此时转换成 0 - 1 背包问题,dp[i][j] 表示从数组nums中 0 - i 中选择元素进行相加可以得到 j 的方法数量// 状态转移方程// j < nums[i] dp[i][j] = dp[i - 1][j]// j >= nums[i] dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i]]int[][] dp = new int[n][neg + 1];dp[0][0] = 1;if(nums[0] == 0) {dp[0][0] = 2;} else if(nums[0] <= neg) {dp[0][nums[0]] = 1;}for(int i = 1; i < n; i++) {int num = nums[i];for(int j = 0; j <= neg; j++) {dp[i][j] = dp[i - 1][j];if(j >= num) {dp[i][j] += dp[i - 1][j - num];}}}return dp[n - 1][neg];}}
动态规划-状态转移方程-一维数组
class Solution {public int findTargetSumWays(int[] nums, int target) {// 记数组之和为 sum,添加符号为 - 的元素之和为 neg,则其余添加符号为 + 的元素之和为 sum - beg// 那么可推导出 (sum - neg) - neg = sum - 2 * neg = target => neg = (sum - target) / 2// 由于数组 nums 中的元素都是非负整数,neg 也必须是非负整数,所以上式成立的前提是 sum - target 是非负偶数。若不符合该条件可直接返回 0。int n = nums.length;int sum = 0;for(int i = 0; i < nums.length; i++) {sum += nums[i];}int diff = sum - target;if(diff < 0 || diff % 2 != 0) {return 0;}int neg = diff / 2;// 此时转换成 0 - 1 背包问题,dp[i][j] 表示从数组nums中 0 - i 中选择元素进行相加可以得到 j 的方法数量// 状态转移方程// j < nums[i] dp[i][j] = dp[i - 1][j]// j >= nums[i] dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i]]// 压缩空间,从二维数组压缩至一维数组int[] dp = new int[neg + 1];dp[0] = 1;if(nums[0] == 0) {dp[0] = 2;} else if(nums[0] <= neg) {dp[nums[0]] = 1;}for(int i = 1; i < n; i++) {int num = nums[i];for(int j = neg; j >= 0; j--) {if(j >= num) {dp[j] += dp[j - num];}}}return dp[neg];}}
322.零钱兑换
回溯
解题思路:
单纯用回溯,会存在大量的重放子问题,需要去掉,使用数组 count 来记录当硬币面额达到 1 ~ amount 之间时,需要的最少硬币数
class Solution {public int coinChange(int[] coins, int amount) {if(amount == 0) {return 0;}return backtrace(coins, amount, new int[amount + 1]);}// backtrace 函数返回的是每个可能出现的 amount 阶段,需要的最少硬币数private int backtrace(int[] coins, int amount, int[] count) {if(amount < 0) {return -1;}if(amount == 0) {return 0;}if(count[amount] != 0) {return count[amount];}int min = Integer.MAX_VALUE;for(int i = 0; i < coins.length; i++) {int res = backtrace(coins, amount - coins[i], count);if(res != -1 && res < min) {min = res + 1;}}count[amount] = min == Integer.MAX_VALUE ? -1 : min;return count[amount];}}
完全背包-求最小值问题
解题思路:将题目转化为完全背包-求最小值问题
class Solution {public int coinChange(int[] coins, int amount) {// 定义状态:// dp[i][j] 表示第 i 个面额的硬币决策完后,金额能达到 j 的最少硬币个数// 状态转移方程为:dp[i][j] = min(dp[i][j - (0 ~ k) * coins])int n = coins.length;int[][] dp = new int[n][amount + 1];for(int i = 0; i < n; i++){Arrays.fill(dp[i], Integer.MAX_VALUE);}// 初始化第一层for(int i = 0; i <= amount / coins[0]; i++) {dp[0][i * coins[0]] = i;}for(int i = 1; i < n; i++) {for(int j = 0; j <= amount; j++) {int k = j / coins[i];for(int c = 0; c <= k; c++) {if(j >= c * coins[i] && dp[i - 1][j - c * coins[i]] != Integer.MAX_VALUE) {dp[i][j] = Math.min(dp[i][j], dp[i - 1][j - c * coins[i]] + c);}}}}return dp[n - 1][amount] == Integer.MAX_VALUE ? -1 : dp[n - 1][amount];}}
518.零钱兑换II
动态规划 - 状态转移方程
class Solution {public int change(int amount, int[] coins) {// 0-1背包问题,计数// 边界条件if(amount == 0) {return 1;}// 定义状态// dp[i] 表示总金额为 i 时,硬币的组合个数// 状态转移方程式: i > coins[j], dp[i] = sum(dp[i - coins[j]])int[] dp = new int[amount + 1];dp[0] = 1;for(int coin : coins) {for(int i = coin; i <= amount; i++) {dp[i] += dp[i - coin];}}return dp[amount];}}
完全背包-求计数问题
class Solution {public int change(int amount, int[] coins) {// 定义状态:dp[i][j] 表示第 i 个面额的硬币决策完后,能到达金额 j 的的硬币组合个数// 状态转移方程:dp[i][j] = sum(dp[i - 1][j - (0 ~ k) * coins[i]])int n = coins.length;int[][] dp = new int[n][amount + 1];// 初始化第一层for(int k = 0; k <= amount / coins[0]; k++) {dp[0][k * coins[0]] = 1;}for(int i = 1; i < n; i++) {int coin = coins[i];for(int j = 0; j <= amount; j++) {int k = j / coin;for(int c = 0; c <= k; c++) {dp[i][j] += dp[i - 1][j - c * coin];}}}return dp[n - 1][amount];}}
路径问题
62.不同路径
动态规划
class Solution {public int uniquePaths(int m, int n) {// 多阶段决策模型 - (m + n - 2) 个阶段// 状态定义: dp[m][n] 为可到达当前网格的总路径数int[][] dp = new int[m][n];// 状态转移方程式:dp[m][n] = dp[m][n - 1] + dp[m - 1][n]// 初始化for(int i = 0; i < m; i++) {dp[i][0] = 1;}for(int i = 0; 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][j - 1] + dp[i - 1][j];}}return dp[m - 1][n - 1];}}
63.不同路径II
动态规划
class Solution {public int uniquePathsWithObstacles(int[][] obstacleGrid) {int m = obstacleGrid.length, n = obstacleGrid[0].length;int[][] dp = new int[m][n];// 状态转移方程// obstacleGrid[i - 1][j] != 1 && obstacleGrid[i][j] != 1 && dp[i - 1][j] != 0, dp[i][j] += dp[i - 1][j]// obstacleGrid[i][j - 1] != 1 && obstacleGrid[i][j] != 1 && dp[i][j - 1] != 0,, dp[i][j] += dp[i][j - 1]for(int i = 0; i < m; i++) {if(obstacleGrid[i][0] == 1) {break;}dp[i][0] = 1;}for(int i = 0; i < n; i++) {if(obstacleGrid[0][i] == 1) {break;}dp[0][i] = 1;}for(int i = 1; i < m; i++) {for(int j = 1; j < n; j++) {if(obstacleGrid[i][j] == 0) {if(obstacleGrid[i][j - 1] != 1 && dp[i][j - 1] != 0) {dp[i][j] += dp[i][j - 1];}if(obstacleGrid[i - 1][j] != 1 && dp[i - 1][j] != 0) {dp[i][j] += dp[i - 1][j];}}}}return dp[m - 1][n - 1];}}
64.最小路径和
动态规划
class Solution {public int minPathSum(int[][] grid) {// 状态定义:dp[m][n] 为左上角到 dp[m][n] 这个格子的最小路径和// 状态转移方程:dp[m][n] = Math.min(dp[m - 1][n], dp[m][n - 1]) + grid[m][n]int m = grid.length, 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] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];}}return dp[m - 1][n - 1];}}
剑指Offer 47.礼物的最大价值
动态规划
class Solution {public int maxValue(int[][] grid) {// 状态定义:dp[m][n] 为左上角到 dp[m][n] 这个格子的最小路径和// 状态转移方程:dp[m][n] = Math.max(dp[m - 1][n], dp[m][n - 1]) + grid[m][n]int m = grid.length, 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] = Math.max(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];}}return dp[m - 1][n - 1];}}
120.三角形最小路径和
动态规划-二维数组
class Solution {public int minimumTotal(List<List<Integer>> triangle) {int size = triangle.size();// 定义状态 dp[i][j] 即为到达 第i行,第j个元素的最小路径// 状态转移方程:// i - 1 >= 0 && j - 1 >= 0 dp[i][j] = Math.min(dp[i - 1][j], dp[i - 1][j - 1]) + triangle.get(i).get(j)// i - 1 > 0 && j == triangle.get(i).size() dp[i][j] = dp[i - 1][j - 1] + triangle.get(i).get(j)int[][] dp = new int[size][size];dp[0][0] = triangle.get(0).get(0);for(int i = 1; i < size; i++) {dp[i][0] = dp[i - 1][0] + triangle.get(i).get(0);}for(int i = 1; i < size; i++) {for(int j = 1; j < triangle.get(i).size(); j++) {if(j == triangle.get(i - 1).size()) {dp[i][j] = dp[i - 1][j - 1] + triangle.get(i).get(j);} else {dp[i][j] = Math.min(dp[i - 1][j], dp[i - 1][j - 1]) + triangle.get(i).get(j);}}}int min = Integer.MAX_VALUE;for(int i = 0; i < size; i++) {min = Math.min(dp[size - 1][i], min);}return min;}}
动态规划-一维数组
class Solution {public int minimumTotal(List<List<Integer>> triangle) {int size = triangle.size();// 定义状态 dp[i][j] 即为到达 第i行,第j个元素的最小路径// 状态转移方程:// i - 1 >= 0 && j - 1 >= 0 dp[i][j] = Math.min(dp[i - 1][j], dp[i - 1][j - 1]) + triangle.get(i).get(j)// i - 1 > 0 && j == triangle.get(i).size() dp[i][j] = dp[i - 1][j - 1] + triangle.get(i).get(j)int[] dp = new int[size];dp[0] = triangle.get(0).get(0);for(int i = 1; i < size; i++) {for(int j = triangle.get(i).size() - 1; j >= 0; j--) {if(j == triangle.get(i - 1).size()){dp[j] = dp[j - 1] + triangle.get(i).get(j);} else if(j == 0) {dp[j] = dp[j] + triangle.get(i).get(j);} else {dp[j] = Math.min(dp[j], dp[j - 1]) + triangle.get(i).get(j);}}}int min = Integer.MAX_VALUE;for(int i = 0; i < size; i++) {min = Math.min(dp[i], min);}return min;}}
打家劫舍 & 买卖股票
198.打家劫舍
动态规划
class Solution {public int rob(int[] nums) {int n = nums.length;int[] dp = new int[n];for(int i = 0; i < n; i++) {if(i - 2 >= 0) {for(int j = i - 2; j >= 0; j--) {dp[i] = Math.max(dp[i], dp[j]);}dp[i] += nums[i];} else {dp[i] += nums[i];}}int max = Integer.MIN_VALUE;for(int i = 0; i < n; i++) {max = Math.max(max, dp[i]);}return max;}}
动态规划-状态转移方程
class Solution {public int rob(int[] nums) {int n = nums.length;// 定义状态:dp[i] 表示当前第一间房到第 i + 1间房之间,能偷窃的最高金额// 定义状态转移方程// 对于第 i 间房有两种选项,偷与不偷// 偷的情况 dp[i] = dp[i - 2] + nums[i];// 不偷的情况 dp[i] = dp[i - 1];// 那么能偷窃的最高金额 dp[i] = max(dp[i - 1], dp[i - 2] + nums[i])int[] dp = new int[n];dp[0] = nums[0];if(n == 1) {return dp[0];}dp[1] = Math.max(nums[0], nums[1]);if(n == 2) {return dp[1];}for(int i = 2; i < n; i++) {dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]) ;}return dp[n - 1];}}
213.打家劫舍II
class Solution {public int rob(int[] nums) {int n = nums.length;// 定义状态:dp[i] 表示当前第一间房到第 i + 1间房之间,能偷窃的最高金额// 定义状态转移方程:dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);// 由于房屋是围成一圈的,第一个房屋和最后一个房屋是紧挨着的,所以当k为最后一间屋时,// 偷的情况 1 ~ k 房之间求 dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);// 不偷的情况 0 ~ k - 1 房之间求 dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);// 比较这两种情况的最大值if(n == 1) {return nums[0];}if(n == 2) {return Math.max(nums[0], nums[1]);}return Math.max(robRange(nums, 0, n - 2), robRange(nums, 1, n - 1));}private int robRange(int[] nums, int start, int end) {int preTwo = nums[start], pre = Math.max(nums[start], nums[start + 1]);for(int i = start + 2; i <= end; i++) {int temp = pre;pre = Math.max(preTwo + nums[i], pre);preTwo = temp;}return pre;}}
337.打家劫舍III (树形DP)
class Solution {public int rob(TreeNode root) {// 定义状态:// 每个节点有偷与不偷状态// money[0] 表示当前节点-不偷累计的最高金额// money[1] 表示当前节点-偷累计的最高金额// Math.max(money[0], money[1]) 表示当前节点累计的最高累计金额// 状态转移方程// 当前节点 node 选择不偷时,money[0] = Math.max(left.menoy[0], left.menoy[1]) + Math.max(right.menoy[0], right.menoy[1])// 当前节点 node 选择偷时, money[1] = left.menoy[0] + right.menoy[0] + node.valint[] money = postorder(root);return Math.max(money[0], money[1]);}private int[] postorder(TreeNode root) {if(root == null) {return new int[]{0, 0};}int[] left = postorder(root.left);int[] right = postorder(root.right);int[] money = new int[2];// 不偷money[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);// 偷money[1] = left[0] + right[0] + root.val;return money;}}
714.买卖股票的最佳时机含手续
class Solution {public int maxProfit(int[] prices, int fee) {// 定义状态:i 从 0 开始// dp[i][0] 表示第 i 天交易完后手里没有股票的最大利润// dp[i][1] 表示第 i 天交易完后手里持有一只股票的最大利润// 状态转移方程:// dp[i][0] = max{dp[i - 1][0], dp[i - 1][1] + prices[i] - fee}// dp[i][1] = max{dp[i - 1][0] - prices[i], dp[i - 1][1]}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++) {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]);}}
309.最佳买卖股票时机含冷冻期
动态规划-1
class Solution {public int maxProfit(int[] prices) {// 定义状态: i 从 0 开始// dp[i][0] 表示第 i 天不持有股票的最大利润// dp[i][1] 表示第 i 天持有股票的最大利润// 状态转移方程:// 当 i == 0 时,结果为0// 当 i < 1 时:// dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i])// dp[i][1] = Math.max(dp[i - 1][0] - prices[i], dp[i - 1][1]);// 当 i > 1 时:// 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]);int n = prices.length;if(n == 1) {return 0;}int[][] dp = new int[n][2];dp[0][0] = 0;dp[0][1] = -prices[0];dp[1][0] = Math.max(dp[0][0], dp[0][1] + prices[1]);dp[1][1] = Math.max(dp[0][0] - prices[1], dp[0][1]);for(int i = 2; i < n; i++) {dp[i][0] = Math.max(dp[i - 1][1] + prices[i], dp[i - 1][0]);dp[i][1] = Math.max(dp[i - 1][1], dp[i - 2][0] - prices[i]);}return Math.max(dp[n - 1][0], dp[n - 1][1]);}}
动态规划-2
class Solution {public int maxProfit(int[] prices) {// 定义状态: i 从 0 开始// dp[i][0] 表示第 i 天持有股票的累计最大利润// dp[i][1] 表示第 i 天不持有股票,且不处于冷冻期的累计最大利润// dp[i][2] 表示第 i 天不持有股票,且处于冷冻期的累计最大利润// 这里的「处于冷冻期」指的是在第 i 天结束之后的状态。也就是说:如果第 i 天结束之后处于冷冻期,那么第 i+1 天无法买入股票。// 状态转移方程:// 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]);// dp[i][2] = dp[i - 1][0] + prices[i];int n = prices.length;int[][] dp = new int[n][3];dp[0][0] = -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], dp[i - 1][2]);dp[i][2] = dp[i - 1][0] + prices[i];}return Math.max(dp[n - 1][1], dp[n - 1][2]);}}
