动态规划
198.打家劫舍
解题思路:
动态规划
- 定义dp数组含义: dp[i]代表当前位置i的最大价值
- 若偷当前位置, 则其最大价值等于dp[i-2]
- 若不偷当前位置, 则最大位置等于dp[i-1]
代码:
class Solution {
public int rob(int[] nums) {
int n = nums.length;
if(n < 2) return nums[0];
// 动态规划 --- 每个位置两个状态 偷 | 不偷 的最大价值
int[][] dp = new int[n][2];
dp[0][0] = 0;
dp[0][1] = nums[0];
dp[1][0] = nums[0];
dp[1][1] = nums[1];
for(int i=2; i < n; i++){
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1]);
dp[i][1] = Math.max(dp[i-2][0], dp[i-2][1]) + nums[i];
}
return Math.max(dp[n-1][0], dp[n-1][1]);
}
}
213.打家劫舍II
解题思路:
两次打家劫舍I
- 若偷第一间, 则最后一间不能偷, 对(0~n-1)进行打家劫舍
- 若不偷第一间, 则最后一间可以偷, 对(1~n)进行打家劫舍
代码:
class Solution {
// 分情况讨论: 偷第一间房子还是不偷第一间房子
// 打家劫舍II---转换成两个打家劫舍I
public int rob(int[] nums) {
int n = nums.length;
if(n < 2) return nums[0];
if(n < 3) return Math.max(nums[0], nums[1]);
// 1.偷第一间屋子, 则最后一间屋子不能偷, 遍历范围就到 (0-n-1)
int[] dp_1 = new int[n-1];
dp_1[0] = nums[0]; dp_1[1] = nums[0];
for(int i=2; i<n-1; i++){
dp_1[i] = Math.max(dp_1[i-1], dp_1[i-2]+nums[i]);
}
// 2.不偷第一间屋子, 则最后一间屋子可以偷, 则遍历范围为 (1-n)
int[] dp_2 = new int[n];
dp_2[0] = 0;
dp_2[1] = nums[1]; dp_2[2] = Math.max(nums[1], nums[2]);
for(int i=3; i<n; i++){
dp_2[i] = Math.max(dp_2[i-1], dp_2[i-2]+nums[i]);
}
// 3.返回这两种的最大值
return Math.max(dp_1[n-2], dp_2[n-1]);
}
}
337.打家劫舍III
动态规划之树形dp
每个节点都有两个状态---偷与不偷, 且其状态都由子节点推导而来, 故需要后序递归遍历二叉树
- 当遍历到null时, 返回{0, 0}
- 当不为null时候, 需要用数组接收子节点的状态
- 得到子节点的状态后, 需要对当前节点进行处理
- 分别判断偷与不偷的价值是多少, 并返回
代码:
class Solution {
// 树形DP
public int rob(TreeNode root) {
int[] ans = dfs(root);
return Math.max(ans[0], ans[1]);
}
// dp数组含义-当前节点状态: dp[0]: 偷, dp[1]: 不偷
public int[] dfs(TreeNode root){
if(root == null) return new int[]{0, 0};
int[] left = dfs(root.left);
int[] right = dfs(root.right);
// 偷当前节点 - 子节点不能偷
int rob = root.val + left[1] + right[1];
// 不偷当前节点, 那么子节点偷不偷取决于偷不偷的金额多少
int noRob = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
return new int[]{rob, noRob};
}
}