198.打家劫舍 213.打家劫舍II 337.打家劫舍III

文章介绍了如何运用动态规划解决“打家劫舍”系列问题,包括避免触发警报系统的策略,通过定义dp数组和推导递推公式计算在不同情境下的最高偷窃金额。
摘要由CSDN通过智能技术生成

198.打家劫舍 213.打家劫舍II 337.打家劫舍III

198.打家劫舍

力扣题目链接(opens new window)

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

  • 示例 1:
  • 输入:[1,2,3,1]
  • 输出:4

解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。 偷窃到的最高金额 = 1 + 3 = 4 。

  • 示例 2:
  • 输入:[2,7,9,3,1]
  • 输出:12 解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。 偷窃到的最高金额 = 2 + 9 + 1 = 12 。

提示:

  • 0 <= nums.length <= 100
  • 0 <= nums[i] <= 400

思路

动态规划五部曲
1.dp数组的及其下标的含义
定义一维dp数组,表示数组下标i和之前所在房屋中,小偷一夜之间得到最高金额
2.推导递推公式
当前下标为i的房屋,如果被小偷光顾.下标i-1的房屋不会被小偷光顾
则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])
3.dp数组初始化
dp[0] = nums[0] dp[0]表示下标为0数组所表示最大金额,金额只有一个那就是nums[0]
dp[1] = Math.max(nums[0],nums[1])
4.遍历顺序
从递推公式中可以看出,dp[i]是由dp[i-1]和dp[i-2]推导而来。所以需要从前向后遍历
5.举例推导dp数组

代码如下

// 时间复杂度: O(n)
// 空间复杂度: O(n)
public int rob(int[] nums) {
    if (nums == null || nums.length == 0)
        return 0;
    if (nums.length == 1) {
        return nums[0];
    }
    if (nums.length == 2) {
        return Math.max(nums[0], nums[1]);
    }
    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; i++) {
        dp[i] = Math.max(dp[i-2] + nums[i], dp[i-1]);
    }
    return dp[nums.length-1];
}

213.打家劫舍II

力扣题目链接(opens new window)

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,能够偷窃到的最高金额。

示例 1:

  • 输入:nums = [2,3,2]
  • 输出:3
  • 解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
  • 示例 2:
  • 输入:nums = [1,2,3,1]
  • 输出:4
  • 解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。偷窃到的最高金额 = 1 + 3 = 4 。
  • 示例 3:
  • 输入:nums = [0]
  • 输出:0

提示:

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 1000

思路

思路:动态规划
数组头部元素和尾部元素不可同时被偷
本题目需要考虑两种情况
第一种是没有数组尾部元素
第二种是没有数组头部元素
分别计算这两种场景,然后得出最大值
动态规划五部曲
1.dp数组的及其下标的含义
定义一维dp数组,表示数组下标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])
3.dp数组初始化
dp[0] = nums[0] dp[0]表示下标为0数组所表示最大金额,金额只有一个那就是nums[0]
dp[1] = Math.max(nums[0],nums[1])
4.遍历顺序
从递推公式中可以看出,dp[i]是由dp[i-1]和dp[i-2]推导而来。所以需要从前向后遍历
5.举例推导dp数组

代码如下

// 时间复杂度: O(n)
// 空间复杂度: O(n)

public int rob(int[] nums) {
    if (nums == null || nums.length == 0)
        return 0;
    if (nums.length == 1) {
        return nums[0];
    }
    if (nums.length == 2) {
        return Math.max(nums[0], nums[1]);
    }

    // 第一种场景,没有数组头部元素
    int[] dp = new int[nums.length];
    dp[1] = nums[1];
    dp[2] = Math.max(nums[1], nums[2]);
    for (int i = 3; i < nums.length; i++) {
        dp[i] = Math.max(dp[i-2] + nums[i], dp[i-1]);
    }

    // 第二种场景没有数组尾部元素
    int[] dp1 = new int[nums.length];
    dp1[0] = nums[0];
    dp1[1] = Math.max(nums[0], nums[1]);
    for (int i = 2; i < nums.length-1; i++) {
        dp1[i] = Math.max(dp1[i-2] + nums[i], dp1[i-1]);
    }

    return Math.max(dp[nums.length-1],dp1[nums.length-2]);
}

337.打家劫舍III

力扣题目链接(opens new window)

在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。

计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
在这里插入图片描述

思路

思路:动态规划
这道题是树形DP的入门题目,通过这道题目大家应该也了解了,所谓树形DP就是在树上进行递归公式的推导。
本题目需要使用树的遍历,才能得出结果,那么使用什么遍历方式呢?
跟前序中序层序相比,后序遍历有个优势,就是可以知道左右子树的返回结果,然后以此做相关判断
所以使用后序遍历,并且需要递归函数来返回值

这道题目属于树形dp的入门题目,因为节点的状态转移是在遍历树节点过程中得到的
所以需要递归三部曲
1.确认递归方法的参数以及返回值

int travel(int dp[], TreeNode node) {
  return Math.max(dp[0], dp[1]);
}

2.终止条件
将当前节点被偷和不被偷的的金钱数组返回
3.核心逻辑

动态规划五部曲
1.dp数组的及其下标的含义
第一次做这道题,dp数组想不出来,看了下题解,这种定义dp数组的方式简直是匪夷所思。
定义一维dp数组,长度为2.dp[0]表示当前节点没有被偷得到的最大金钱,dp[1]表示当前节点被偷得到的最大金钱.
长度为2的数组怎么标记树中每个节点的状态呢?
因为在递归过程中,系统栈会保存每一层参数
2.推导递推公式
如果偷当前节点,那么下一层节点不能被偷。dp[1] =左子树dp[0] + 右子树dp[0] + node.val
如果不偷当前节点,那么左右孩子可以被偷,也可以不被偷,故从被偷和不被偷中找最大值.dp[0] = Math.max(左子树dp[0],左子树dp[1]) + Math.max(右子树dp[0],右子树dp[1])
3.dp数组初始化
直接默认dp数组的值均为0即可
4.遍历顺序
后续遍历
5.举例推导dp数组

代码如下

// 时间复杂度:O(n),每个节点只遍历了一次
// 空间复杂度:O(log n),算上递推系统栈的空间
public static void main(String[] args) {
    TreeNode root5 = new TreeNode(1, null, null);
    TreeNode root4 = new TreeNode(3, null, null);
    TreeNode root2 = new TreeNode(3, null, root5);
    TreeNode root1 = new TreeNode(2, null, root4);
    TreeNode root = new TreeNode(3, root1, root2);
    rob(root);
}


public static int rob(TreeNode root) {
    if (root == null)
        return 0;
    int[] dp = new int[2];// 定义并初始化dp数组
    travel(dp, root);
    return Math.max(dp[0], dp[1]);// 返回根节点被偷和不被偷金额最大值
}

public static int[] travel(int dp[], TreeNode node) {
    if (node == null)// 终止条件
        return new int[]{0, 0};

    int[] leftDp = travel(new int[2], node.left);
    int[] rightDp = travel(new int[2], node.right);
    dp[0] = Math.max(leftDp[0], leftDp[1]) + Math.max(rightDp[0], rightDp[1]);
    dp[1] = leftDp[0] + rightDp[0] + node.val;
    return dp;
}

问题

推导递推公式时,理解错了两点
1.以为一层的节点,要么都选,要么都不选
2.一层的节点只选一个
这两个错误导致我写出错误的递推公式

dp[1] = Math.max(左子树dp[0] , 右子树dp[0]) + node.val 以为左右子树只会选择一个
dp[0] = Math.max ( 左子树dp[0]+右子树dp[0] , 左子树dp[1] + 右子树dp[1] )

二刷没做出来
可以想到递归和后序遍历思路,但无法想到dp数组的定义

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值