环形房屋打家劫舍算法

环形房屋打家劫舍题解

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. 边界条件需特殊处理(空数组、单元素数组),避免逻辑漏洞。
相关推荐
智驱力人工智能2 小时前
守护矿山动脉 矿山皮带跑偏AI识别系统的工程化实践与价值 皮带偏离检测 皮带状态异常检测 多模态皮带偏离监测系统
大数据·人工智能·opencv·算法·安全·yolo·边缘计算
智驱力人工智能2 小时前
构筑安全红线 发电站旋转设备停机合规监测的视觉分析技术与应用 旋转设备停机检测 旋转设备异常检测 设备停机AI行为建模
人工智能·opencv·算法·安全·目标检测·计算机视觉·边缘计算
宵时待雨2 小时前
数据结构(初阶)笔记归纳2:顺序表的实现
c语言·数据结构·笔记·算法
寻星探路2 小时前
【算法进阶】滑动窗口与前缀和:从“和为 K”到“最小覆盖子串”的极限挑战
java·开发语言·c++·人工智能·python·算法·ai
不穿格子的程序员2 小时前
从零开始刷算法——二叉树篇:层序遍历 + 有序数组转二叉搜索树
算法
kaikaile19952 小时前
同伦算法求解非线性方程组的MATLAB实现与优化
开发语言·算法·matlab
cici158742 小时前
计算四连杆机构的运动学
线性代数·算法·机器学习
南烟斋..2 小时前
Linux系统编程核心知识指南
linux·算法
Jerryhut2 小时前
光流估计从原理到实战:基于 Lucas-Kanade 算法与 OpenCV 实现
人工智能·opencv·算法