Free Talk
终于轮到 LeetCode 的大头了, 动态规划应该是占据了算法题的半壁江山,我们废话不多说,直接上代码。
Talk is cheap, Show me your Code!
Finished Problem
最小路径和
64. 最小路径和
动态规划
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class Solution { public int minPathSum(int[][] grid) { if(grid.length == 0) return 0;
int m = grid.length; int n = grid[0].length;
int[][] dp = new int[m][n]; for(int i = 0; i < m; i ++ ){ for(int j = 0; j < n ; j ++){ if(i == 0 && j == 0) dp[0][0] = grid[0][0]; else if(i == 0 && j != 0) dp[i][j] = dp[i][j - 1] + grid[i][j]; else if(j == 0 && i != 0) dp[i][j] = dp[i - 1][j] + grid[i][j]; else dp[i][j] = Math.min(dp[i - 1][j],dp[i][j - 1]) + grid[i][j]; } } return dp[m - 1][n - 1]; } }
|
执行结果
刷完这题,终于把 LeetCode 刷到了 50 题,总算是入门了,开心!!!
不同的二叉搜索树
96. 不同的二叉搜索树
二叉搜索树
二叉查找树(BST:Binary Search Tree)是一种特殊的二叉树,它改善了二叉树节点查找的效率。二叉查找树有以下性质:
对于任意一个节点 n,
- 其左子树(left subtree)下的每个后代节点(descendant node)的值都小于节点 n 的值;
- 其右子树(right subtree)下的每个后代节点的值都大于节点 n 的值。
所谓节点 n 的子树,可以将其看作是以节点 n 为根节点的树。子树的所有节点都是节点 n 的后代,而子树的根则是节点 n 本身。
Reference:https://www.cnblogs.com/gaochundong/p/binary_search_tree.html
动态规划
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Solution { public int numTrees(int n) { if(n == 0) return 0;
int[] dp = new int[n + 1]; dp[0] = 1; dp[1] = 1;
for(int i = 2; i < n + 1; i ++ ){ for(int j = 1 ; j <= i ; j ++ ){ dp[i] += dp[j - 1]*dp[i - j]; } }
return dp[n]; } }
|
执行结果
单词拆封
139. 单词拆分
动态规划 + 哈希表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| class Solution {
HashMap<String, Boolean> map = new HashMap<>();
public boolean wordBreak(String s, List<String> wordDict) {
if(s.length() == 0) return false; boolean[] dp = new boolean[s.length() + 1]; dp[0] = true;
for(String word: wordDict){ map.put(word,true); } for(int j = 1; j <=s.length(); j ++ ){ for(int i = j - 1; i >= 0; i -- ){ dp[j] = dp[i] && check(s.substring(i, j)); if(dp[j]) break; } }
return dp[s.length()]; }
public boolean check(String word){ return map.getOrDefault(word,false); } }
|
执行结果
乘积最大子数组
失败解法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Solution { public int maxProduct(int[] nums) { int[] dp = new int[nums.length + 1]; dp[0] = 1; int max = Integer.MIN_VALUE;
for(int j = 1 ; j <= nums.length ; j ++ ){ if(j == 1){ dp[j] = nums[j - 1]; max = dp[j]; continue; } if(dp[j - 1] * nums[j - 1] > dp[j - 1] || nums[j - 1] > dp[j - 1]){ dp[j] = Math.max(dp[j - 1] * nums[j - 1], nums[j - 1]); max = Math.max(max, dp[j]); } else dp[j] = 1; }
return max; } }
|
动态规划 + 维护最小值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class Solution { public int maxProduct(int[] nums) {
int max = Integer.MIN_VALUE, imax = 1, imin = 1; for(int num : nums){ if(num < 0){ int tmp = imax; imax = imin; imin = tmp; }
imax = Math.max(imax * num, num); imin = Math.min(imin * num, num);
max = Math.max(max, imax); }
return max; } }
|
执行结果
打家劫舍
198. 打家劫舍
动态规划 + 数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Solution { public int rob(int[] nums) {
if(nums.length == 0) return 0;
int[] dp = new int[nums.length + 1]; dp[0] = 0; dp[1] = nums[0];
for(int j = 2; j <= nums.length ; j ++ ){ dp[j] = Math.max(dp[j - 2] + nums[j - 1], dp[j - 1]); }
return dp[nums.length]; } }
|
执行结果
最大正方形
221. 最大正方形
四个角
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| class Solution { public int maximalSquare(char[][] matrix) { if(matrix == null || matrix.length == 0) return 0;
int[][] dp = new int[matrix.length][matrix[0].length]; int maxSide = 0;
for(int i = 0; i < matrix.length ; i ++ ){ for(int j = 0; j < matrix[0].length ; 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], Math.min(dp[i - 1][j - 1], dp[i][j - 1])) + 1; } maxSide = Math.max(maxSide, dp[i][j]); } } }
return maxSide * maxSide; } }
|
执行结果
完全平方数
279. 完全平方数
转换思维 + 平方和
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Solution { public int numSquares(int n) {
int[] dp = new int[n + 1]; for(int i = 1; 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]; } }
|
执行结果
最佳买卖股票时机含冷冻期
309. 最佳买卖股票时机含冷冻期
定义三个状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class Solution { public int maxProfit(int[] prices) {
if(prices.length == 0 || prices.length < 2) return 0; int[][] dp = new int[prices.length][3]; int maxSales = 0;
dp[0][0] = - 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][2], dp[i - 1][1]); dp[i][2] = dp[i - 1][0] + prices[i]; }
maxSales = Math.max(dp[prices.length - 1][1],dp[prices.length - 1][2]);
return maxSales; } }
|
执行结果
戳气球
312. 戳气球
区间定义 + 动态规划
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| class Solution {
public int maxCoins(int[] nums) {
int n = nums.length; int[] points = new int[n + 2]; points[0] = points[n + 1] = 1;
for(int i = 1; i < n + 1; i ++){ points[i] = nums[i - 1]; }
int[][] dp = new int[n + 2][n + 2];
for(int i = n; i >= 0; i -- ){ for(int j = i + 1; j < n + 2; j ++ ){ for(int k = i + 1; k < j; k ++){ dp[i][j] = Math.max( dp[i][j], dp[i][k] + dp[k][j] + points[i]*points[k]*points[j] ); } } }
return dp[0][n + 1]; } }
|
执行结果
零钱兑换
322. 零钱兑换
遍历数组 + 动态规划
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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 + 1; i ++ ){ for(int coin: coins){ if(i - coin < 0) continue; dp[i] = Math.min(dp[i - coin] + 1, dp[i]); } } return (dp[amount] == amount + 1 )? -1: dp[amount]; } }
|
执行结果
比特位计数
338. 比特位计数
奇偶判断 + 动态规划
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Solution { public int[] countBits(int num) {
int[] dp = new int[num + 1]; dp[0] = 0;
for(int i = 1 ; i < num + 1; i ++ ){ if(i % 2 == 1) dp[i] = dp[i - 1] + 1; else dp[i] = dp[i / 2]; } return dp; } }
|
执行结果
分割等和子集
416. 分割等和子集
0-1 背包问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| class Solution { public boolean canPartition(int[] nums) {
if(nums.length == 0) return false;
int sum = 0; for(int num: nums){ sum += num; }
if(sum % 2 == 1) return false; int target = sum / 2; boolean[][] dp = new boolean[nums.length][target + 1];
if(nums[0] < target){ dp[0][nums[0]] = true; }
for(int i = 1; i < nums.length ; i ++ ){ for(int j = 0; j < target + 1; j ++ ){ dp[i][j] = dp[i - 1][j];
if(nums[i] == j){ dp[i][j] = true; continue; } if(nums[i] < j){ dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]]; } } }
return dp[nums.length - 1][target]; } }
|
执行结果
目标和
494. 目标和
带加减的背包问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| class Solution { public int findTargetSumWays(int[] nums, int S) {
int sum = 0; for(int num: nums){ sum += num; }
if(Math.abs(sum) < Math.abs(S)) return 0;
int t = 2*sum + 1; int[][] dp = new int[nums.length][t];
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 < nums.length ; i ++){ for(int j = 0; j < t ; j ++){ int left = (j - nums[i]) >= 0 ? j - nums[i] : 0; int right = (j + nums[i]) < t ? j + nums[i] : 0; dp[i][j] = dp[i - 1][left] + dp[i - 1][right]; } }
return dp[nums.length - 1][S + sum]; } }
|
执行结果
完结撒花
其实我挺失落的,因为动态规划的问题,属于那种刷着很爽,但是真的太难想出状态转移方程了。真的是不看题解一筹莫展,看了之后豁然开朗。打算晚上写一篇动态规划的总结方法,不能让这么多题白刷。
定义数组元素的含义
找出状态转移方程
给出初始值
优化
参考链接