环形房屋打家劫舍算法

环形房屋打家劫舍题解

1. 问题描述

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

2. 核心思路分析

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

  • 问题具备重叠子问题 :计算第 i 间房屋的最大可盗窃金额时,需要重复用到前 i-1、i-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)。仅使用了常数个临时变量(pre、cur、temp 等),未额外开辟与数组长度相关的空间。

总结

  1. 环形房屋打家劫舍的核心是拆解问题:将环形转化为"不偷首"或"不偷尾"的两个线性问题,规避首尾相邻的冲突;
  2. 线性问题的最优解通过动态规划+空间优化实现,用两个变量滚动更新,兼顾时间和空间效率;
  3. 边界条件需特殊处理(空数组、单元素数组),避免逻辑漏洞。
相关推荐
xuxigifxfh17 分钟前
牛客:HJ21 简单密码
java·开发语言·算法
qq_4086068620 分钟前
确界的倒数性质证明
学习·算法
小蒋观天下26 分钟前
【安防AI工程实战二】AI摄像头帧率抖动根治:ISP曝光时序与AI推理时序冲突深度定位
人工智能·深度学习·算法·安全·机器学习·ai大模型
云和数据.ChenGuang37 分钟前
trace 是RAG检索链路的**全链路追踪日志对象
人工智能·深度学习·算法·排序算法
Evand J40 分钟前
【PMSM例程6】永磁同步电机(PMSM)基于增广EKF的d-q轴电感分阶段辨识算法|电机参数估计与误差分析
算法·matlab·电机·ekf
垆边人似月.41 分钟前
华为题:最多可以参加的会议数目
数据结构·c++·算法
Matlab程序猿44 分钟前
【MATLAB源码-第460期】基于matlab的OTFS通信系统SLM峰均比抑制及非线性功率放大器失真与误码性能仿真
开发语言·算法·matlab·slm·otfs
xiangyun611 小时前
C++ list 模拟实现(一):手撕双向链表,从节点到迭代器
数据库·c++·算法·链表·stl·list
码爸9 小时前
线性回归算法
算法·机器学习·线性回归
Coder-magician10 小时前
《代码随想录》刷题打卡day51:图论-part09(dijistra堆优化,Bellman_ford算法)
算法·图论