环形房屋打家劫舍算法

环形房屋打家劫舍题解

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. 边界条件需特殊处理(空数组、单元素数组),避免逻辑漏洞。
相关推荐
CoderCodingNo3 小时前
【GESP】C++五级练习题 luogu-P1865 A % B Problem
开发语言·c++·算法
大闲在人4 小时前
7. 供应链与制造过程术语:“周期时间”
算法·供应链管理·智能制造·工业工程
小熳芋4 小时前
443. 压缩字符串-python-双指针
算法
Charlie_lll4 小时前
力扣解题-移动零
后端·算法·leetcode
chaser&upper4 小时前
矩阵革命:在 AtomGit 解码 CANN ops-nn 如何构建 AIGC 的“线性基石”
程序人生·算法
weixin_499771554 小时前
C++中的组合模式
开发语言·c++·算法
觉醒大王4 小时前
哪些文章会被我拒稿?
论文阅读·笔记·深度学习·考研·自然语言处理·html·学习方法
iAkuya4 小时前
(leetcode)力扣100 62N皇后问题 (普通回溯(使用set存储),位运算回溯)
算法·leetcode·职场和发展
近津薪荼4 小时前
dfs专题5——(二叉搜索树中第 K 小的元素)
c++·学习·算法·深度优先
xiaoye-duck5 小时前
吃透 C++ STL list:从基础使用到特性对比,解锁链表容器高效用法
c++·算法·stl