动态规划
- 动态规划(Dynamic Programming),简称DP,如果某一问题有很多重叠子问题,使用动态规划是最有效的。
- 动态规划中每一个状态一定是由上一个状态推导出来的。
解题五部曲
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
下面运用这五步解决下面多个例题:
LeetCode70. 爬楼梯
1.确定dp数组(dp table)以及下标的含义
dp[i]数组表示,爬到第i层楼梯,有dp[i]种方法。
2. 确定递推公式
根据定义dp[i]等于爬上i层的方法数量,动态规划中当前状态是由上一个状态推断出来的,由于一次只能爬一或者二个台阶,那么和容易想到dp[i]应该是和dp[i - 1]以及dp[i - 2]有关系。 dp[i - 1]跳一台阶就是dp[i], dp[i - 2]跳两台阶就是dp[i],两者相加的方法总和便是dp[i]!
3. dp数组如何初始化
在这里很显然,dp[1] = 1, dp[2] = 2, 初始化完成,这里不应该讨论dp[0],没有意义,因为只能根据dp[2]的结果逆推dp[0]为1, 也就是说在0层不动也是一种方法,这其实就有些荒谬了hh
4. 确定遍历顺序
dp[i] = dp[i - 1] + dp[i - 2],很显然是从前往后便利的
5. 举例推导dp数组
举例得: dp[1] = 1, dp[2] = 2, dp[3] = 3, dp[4] = 5, 验证成功。
- 代码如下
class Solution {
public int climbStairs(int n) {
int[] dp = new int[n + 1];
if (n <= 1) {
return n;
}
dp[1] = 1;
dp[2] = 2;
for (int i = 3; i < n + 1; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
}
LeetCode5. 最长回文子串
代码如下:
class Solution {
public String longestPalindrome(String s) {
int len = s.length();
//1. dp数组的意义为以i,j为边界的字符串是否为回文子串
//2. 初始化dp皆为false;
boolean[][] dp = new boolean[len][len];
int start = 0;
int end = 0;
int maxLen = 0;
//4. 递归顺序应该是从下往上,从左往右的,因为dp[i][j]的值需要dp[i + 1][j - 1]来确定
for (int i = len - 1; i >= 0; i--) {
for (int j = i ; j < len; j++) {
//3. 递归逻辑: j 与 i 相差为1或者0的时候说明必然是回文子串,否则根据dp[i + 1][j - 1]来确定!
if (s.charAt(i) == s.charAt(j)) {
if (j - i <= 1) {
if (j - i > maxLen) {
start = i;
end = j;
maxLen = j - i;
}
dp[i][j] = true;
} else {
if (dp[i + 1][j - 1] == true) {
if (j - i > maxLen) {
start = i;
end = j;
maxLen = j - i;
}
dp[i][j] = true;
}
}
}
}
}
return s.substring(start, end + 1);
}
}
LeetCode198. 打家劫舍
class Solution {
public int rob(int[] nums) {
if (nums == null) {
return 0;
}
if (nums.length == 1) {
return nums[0];
}
int[] dp = new int[nums.length];
//1. dp[i]:考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i]。
//3. 初始化
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);
//4. 确定遍历顺序 dp[i] 是根据dp[i - 2] 和 dp[i - 1] 推导出来的,那么一定是从前到后遍历!
for (int i = 2; i < nums.length; i++) {
//2. 如果偷第i房间,那么dp[i] = dp[i - 2] + nums[i]。如果不偷第i房间,那么dp[i] = dp[i - 1]
dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp[nums.length - 1];
}
}
这里有个升级版就是环型数组打家劫舍,比较两种情况即可,情况一: 不含尾元素, 情况二: 不含头元素。
LeetCode337. 打家劫舍(二叉树版)
这题就直接看卡尔哥的解析就很好了😁 programmercarl.com/0337.%E6%89…
代码如下:
class Solution {
public int rob(TreeNode root) {
int[] res = robTree(root);
return Math.max(res[0], res[1]);
}
public int[] robTree(TreeNode node) {
// dp[0]表示不偷该节点能盗取的最高金额,dp[1]表示偷该节点能盗取的最高金额。
int[] dp = new int[2];
if (node == null) {
dp[0] = 0;
dp[1] = 0;
return dp;
}
int[] left = robTree(node.left);
int[] right = robTree(node.right);
dp[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
dp[1] = node.val + left[0] + right[0];
return dp;
}
}
- 本文是代码随想录的学习笔记, 感谢卡尔哥的教学!!!🥰