环形房屋打家劫舍算法

环形房屋打家劫舍题解

1. 问题描述

所有房屋首尾相连形成环形排列 (第一个房屋和最后一个房屋相邻),相邻房屋装有联动防盗系统,若同时盗窃相邻两间房屋会触发警报。给定一个非负整数数组 nums,其中 nums[i] 表示第 i 间房屋存放的金额,要求计算在不触发警报的前提下,能盗窃到的最大金额。

2. 核心思路分析

本题的核心是动态规划的应用,原因如下:

  • 问题具备重叠子问题 :计算第 i 间房屋的最大可盗窃金额时,需要重复用到前 i-1i-2 间房屋的计算结果;
  • 问题具备最优子结构:全局的最大金额可由每个位置的局部最优解推导而来。

而环形结构是本题的关键难点:由于首尾房屋相邻,"同时盗窃首尾"是非法的。因此我们可以将环形问题拆解为两个线性问题,规避首尾冲突:

  • 情况1:不考虑最后一间房屋(只盗窃 [0, n-2] 区间的房屋);
  • 情况2:不考虑第一间房屋(只盗窃 [1, n-1] 区间的房屋)。

最终答案即为这两个线性问题的最大值------因为这两种情况已覆盖所有"不触发警报"的合法盗窃方式(要么不偷首,要么不偷尾,不可能同时偷首尾)。

对于线性房屋的打家劫舍问题,动态规划的状态转移方程为:

dp[i] 表示盗窃到第 i 间房屋时能获得的最大金额,则:
dp[i] = max(dp[i-1], dp[i-2] + nums[i])

  • dp[i-1]:不偷第 i 间房屋,最大金额等于前 i-1 间的最优解;
  • dp[i-2] + nums[i]:偷第 i 间房屋,此时第 i-1 间不能偷,最大金额等于前 i-2 间的最优解加上第 i 间的金额。

此外,我们可以对空间进行优化:无需维护完整的 dp 数组,只需用两个变量 pre(表示 dp[i-2])和 cur(表示 dp[i-1])滚动更新,将空间复杂度从 O(n) 降至 O(1)

3. 解题步骤

  1. 边界条件处理
    • 若数组为空(n=0),返回 0;
    • 若数组只有1间房屋(n=1),直接返回该房屋的金额(无相邻冲突)。
  2. 定义辅助函数 :实现线性区间 [start, end] 内的最大盗窃金额计算逻辑:
    • 初始化 pre=0(前前一个位置的最优解)、cur=0(前一个位置的最优解);
    • 遍历区间内的每间房屋,通过滚动变量更新当前最优解;
  3. 拆分环形问题
    • 计算 [0, n-2] 区间的最大金额(不偷最后一间);
    • 计算 [1, n-1] 区间的最大金额(不偷第一间);
  4. 返回结果:取上述两个结果的最大值。

4. 代码实现

cpp 复制代码
class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        // 边界条件1:无房屋可偷
        if (n == 0) return 0;
        // 边界条件2:只有1间房屋,直接偷
        if (n == 1) return nums[0];
        // 拆分为两个线性问题,取最大值
        int res1 = robLinear(nums, 0, n-2); // 不偷最后一间
        int res2 = robLinear(nums, 1, n-1); // 不偷第一间
        return max(res1, res2);
    }

    // 辅助函数:计算线性区间[start, end]内的最大盗窃金额
    int robLinear(vector<int>& nums, int start, int end) {
        int pre = 0;  // 代表dp[i-2],前前一个位置的最优解
        int cur = 0;  // 代表dp[i-1],前一个位置的最优解
        for (int i = start; i <= end; ++i) {
            int temp = cur; // 暂存当前cur(更新前的dp[i-1])
            // 更新cur为当前位置i的最优解:max(不偷i, 偷i)
            cur = max(pre + nums[i], cur);
            pre = temp; // pre更新为原来的cur(即新的dp[i-1])
        }
        return cur; // 最终cur为区间[start, end]的最优解
    }
};

5. 复杂度分析

  • 时间复杂度O(n)。仅需两次线性遍历数组([0,n-2][1,n-1]),每次遍历的时间为 O(n),整体仍为 O(n)
  • 空间复杂度O(1)。仅使用了常数个临时变量(precurtemp 等),未额外开辟与数组长度相关的空间。

总结

  1. 环形房屋打家劫舍的核心是拆解问题:将环形转化为"不偷首"或"不偷尾"的两个线性问题,规避首尾相邻的冲突;
  2. 线性问题的最优解通过动态规划+空间优化实现,用两个变量滚动更新,兼顾时间和空间效率;
  3. 边界条件需特殊处理(空数组、单元素数组),避免逻辑漏洞。
相关推荐
To_OC7 小时前
LC 131 分割回文串:刚学回溯时,我连怎么切字符串都想不明白
javascript·算法·leetcode
旖-旎8 小时前
LeetCode 518:零钱兑换||(完全背包)—— 题解
c++·算法·leetcode·动态规划·背包问题
To_OC8 小时前
LC 42 接雨水:暴力超时卡半天?前后缀数组一用就通了
javascript·算法·leetcode
delishcomcn9 小时前
AI视觉识别+分切算法:电化铝缺陷检测与裁切一体化解锁
人工智能·算法
触底反弹9 小时前
深入理解大模型采样:Temperature、Top-K、Top-P 的原理与实战
人工智能·算法·面试
雪碧聊技术10 小时前
力扣 LCR 091. 粉刷房子 —— 动态规划入门详解
算法·动态规划
CV-Climber12 小时前
检索技术的实际应用
人工智能·算法
hhzz13 小时前
Tiger AI Platform平台中增加人脸识别功能
图像处理·人工智能·算法·计算机视觉·大模型
从零开始的代码生活_14 小时前
C++ 继承详解:访问控制、对象模型、菱形继承与设计取舍
开发语言·c++·后端·学习·算法