视频讲解:
动态规划,偷不偷这个房间呢?| LeetCode:198.打家劫舍_哔哩哔哩_bilibili
198. 打家劫舍
思路:其实和爬楼梯的题目非常的类似,但是缺少了每次可以爬一级的思想,因此状态转移的过程就是从2开始,统计dp[j-2……k]+nums[j] 这样统计。自己想了一种方法,是采用间隔遍历的方式,每个位置都统计之前的所有位置所传递而来的最大值,但是所形成的结果就是完全区别了dp[n]和dp[n-1]两个位置的结果,所以最后两者需要比较一下输出最大值。
// 时间复杂度O(n^2)
// 空间复杂度O(n)
// class Solution {
// public int rob(int[] nums) {
// // 初步感觉,类似于爬楼梯,但是每次爬一级楼梯不行
// if(nums.length == 1)
// return nums[0];
// if(nums.length == 2)
// return Math.max(nums[0], nums[1]);
// int[] dp = new int[nums.length+1];
// // 房屋的金额视作为物品
// // 第几个房屋视作为重量
// dp[0] = 0;
// dp[1] = nums[0];
// dp[2] = nums[1];
// for(int i=3; i<=nums.length; i++){
// for(int j=2; i-j>=0; j++){
// dp[i] = Math.max(dp[i], dp[i-j]+nums[i-1]);
// }
// }
// return Math.max(dp[nums.length], dp[nums.length-1]);
// }
// }
// 时间复杂度O(n)
// 空间复杂度O(n)
class Solution {
public int rob(int[] nums) {
if (nums == null || nums.length == 0) return 0;
if (nums.length == 1) return nums[0];
// dp表示的是到第 i+1 户人家可以偷取的最大金额
// 这种dp策略是涉及每个位置的最大值,是相邻位置也需要进行考虑,而我上面的那种dp其实是间隔的位置的最大值
int[] dp = new int[nums.length];
// 初始化
dp[0] = nums[0];
dp[1] = Math.max(dp[0], nums[1]);
for (int i = 2; i < nums.length; i++) {
// dp[i - 1]表示当前i位置不偷,偷i-1位置所取得的金额,与当前偷了i位置取得的金额取较大的一方
dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
}
// 那么我们可以对打家劫舍问题作出一点心得:
// 1、在i位置,所考虑的来源仅仅是i-1;另外状态之间的转换上,i位置始终依赖的就是i-2的,关于这一点,因为我们遍历顺序是从头开始,且dp内总是存储的最优值,因此在后续遍历i时,只需要考虑可行的i-2,与先前的最优化进一步关联
// 2、递推公式的核心就是偷i与不偷i所取得的收益进行比较求最优,甚至本质思想是依赖贪心来进行的
return dp[nums.length - 1];
}
}
213.打家劫舍II
思路:本题相较于 打家劫舍I 所区别在于数组是循环的,即i位置往后遍历与 打家劫舍I 遍历的方式一致,但是i-1 位置不可以再访问。递推公式与 打家劫舍I 的公式一致,而遍历顺序上,可从数组的任一位置开始,因此我们可以选择从数组的0位置开始,并且注意特殊的nums.length-1位置是从0位置开始遍历时不可以再接触的一个位置。
其实归根到底这么想,是因为打家劫舍系列的题目,i位置的状态迁移总是与i-1和i-2两个位置相关,i继承于i-2,i-1是i所参考对比的,那么在访问过程中,以及最后输出结果的时候,需要判别的就是dp[n] dp[n-1]两个位置;同样对于本题循环,设置两个方向,从0和nums.length-1处开始遍历即可求解,这和 打家劫舍I 中第一种解法的想法是贯通的。
// 时间复杂度O(n)
// 空间复杂度O(2n)
class Solution {
public int rob(int[] nums) {
if(nums.length == 1)
return nums[0];
if(nums.length == 2)
return Math.max(nums[0], nums[1]);
if(nums.length == 3)
return Math.max(nums[0], Math.max(nums[1], nums[2]));
int n = nums.length;
int[] dp = new int[nums.length];
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] = nums[n-1];
DP[1] = Math.max(nums[n-1], nums[0]);
for(int i=2; i<nums.length-1; i++){
int index = (n-1+i)%n;
DP[i] = Math.max(DP[i-1], DP[i-2]+nums[index]);
}
return Math.max(dp[n-2], DP[n-2]);
}
}
337.打家劫舍 III
思路:树形结构的动态规划题目,本题不会,做不出来。题解先理解背诵。树形结构的题目,确定先序/中序/后序的遍历顺序非常的重要。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
// 时间复杂度O(n),n为节点的个数
// 空间复杂度O(2n)
class Solution {
public int rob(TreeNode root) {
int[] res = robAction(root);
return Math.max(res[0], res[1]);
}
public int[] robAction(TreeNode root){
if(root == null)
return new int[]{0,0};
int[] res = new int[2];
// 后序遍历操作,优先完成左右孩子金额的计算,用来配合父节点呃金额的计算,并且父节点的金额确实依赖子节点
int[] left = robAction(root.left);
int[] right = robAction(root.right);
res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
res[1] = root.val + left[0] + right[0];
return res;
}
}