Skip to content

打家劫舍 II

约 770 字大约 3 分钟

动态规划递推分类

2025-02-25

213. 打家劫舍 II

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

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

示例 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 = [1,2,3]
输出:3

提示:

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

动态规划-数组递推,分类

分成两类。dp1[i-1]表示不包括第一个房子时前 i 个房子能偷到的最大金额,dp2[i-1]表示不包括最后一个房子时前 i 个房子能偷到的最大金额,返回两种情况中较大的一个

class Solution {
    public int rob(int[] nums) {
        int n = nums.length;
        
        // 如果只有一个房子,直接返回该房子的金额
        if (n == 1) return nums[0];
        // 如果有两个房子,返回两个房子中较大的金额
        if (n == 2) return Math.max(nums[0], nums[1]);

        // dp1[i]表示不包括第一个房子时从房子[0...i]能偷到的最大金额
        int[] dp1 = new int[n];
        dp1[1] = nums[1]; // 第一个房子不偷,dp1[0]=0,从第二个房子开始,索引为1
        for (int i = 2; i < n; i++) {
            // 状态转移方程:dp1[i] = max(dp1[i - 1], dp1[i - 2] + nums[i])
            dp1[i] = Math.max(dp1[i - 1], dp1[i - 2] + nums[i]);
        }

        // dp2[i]表示不包括最后一个房子时从房子[0...i]能偷到的最大金额
        int[] dp2 = new int[n];
        dp2[0] = nums[0]; // 可以偷第一个房子
        dp2[1] = Math.max(nums[0], nums[1]); // 选择第一个房子和第二个房子中较大的一个
        for (int i = 2; i < n - 1; i++) { // 注意这里只遍历到 n - 2,因为最后一个房子不能偷,从而dp2[n-1]为默认值0
            // 状态转移方程:dp2[i] = max(dp2[i - 1], dp2[i - 2] + nums[i])
            // 转移方程没变,只是初始值变了。
            dp2[i] = Math.max(dp2[i - 1], dp2[i - 2] + nums[i]);
        }

        // 返回两种情况中较大的一个
        return Math.max(dp1[n - 1], dp2[n - 2]);
    }
}
  • 时间复杂度:O(n),因为遍历了两次数组,每次数组长度为 n。
  • 空间复杂度:O(n),因为使用了两个长度为 n 的数组 dp1 和 dp2。