每日一题——打家劫舍

打家劫舍(一)与打家劫舍(二)动态规划解法详解

打家劫舍(一)

问题描述

你是一个经验丰富的小偷,准备偷沿街的一排房间,每个房间都存有一定的现金。不能偷相邻的两家 。给定一个整数数组 nums,数组中的元素表示每个房间存有的现金数额,计算在不被发现的前提下最多的偷窃金额。

数据范围

数组长度满足 (1 \leq n \leq 2 \times 10^5),数组中每个值满足 (1 \leq \text{nums}[i] \leq 5000)。


示例

输入 输出 说明
[1, 2, 3, 4] 6 偷第 2、4 个房间
[1, 3, 6] 7 偷第 1、3 个房间
[2, 10, 5] 10 偷第 2 个房间

解题思路

动态规划
  1. 状态定义

    • dp[i] 表示考虑前 i 个房间时的最大偷窃金额。
  2. 状态转移方程

    • 当前房间有两种选择:
      • 不偷 :继承前一个房间的最大金额(dp[i-1])。
      • :当前房间金额 + 前前一个房间的最大金额(dp[i-2] + nums[i])。
    • 因此:

      dp\[i\] = \\max(dp\[i-1\], \\ dp\[i-2\] + \\text{nums}\[i\])

  3. 边界条件

    • dp[0] = nums[0]:只有一个房间时只能偷它。
    • dp[1] = \max(nums[0], nums[1]):两个房间时偷金额较大的。

代码实现

c 复制代码
int rob(int* nums, int numsLen) {
    // 如果数组长度小于等于0,说明没有房子可以偷,直接返回0
    if (numsLen <= 0) return 0;

    // 如果数组长度为1,说明只有一间房子,直接返回这间房子的金额
    if (numsLen == 1) return nums[0];

    // 如果数组长度为2,说明有两间房子,选择金额较大的那间房子
    if (numsLen == 2) return (nums[0] > nums[1]) ? nums[0] : nums[1];

    // 定义一个动态规划数组dp,dp[i]表示到第i间房子为止,小偷能偷到的最大金额
    int dp[numsLen];

    // 初始化dp数组的前两个值
    // 如果只有一间房子,最大金额就是这间房子的金额
    dp[0] = nums[0];
    // 如果有两间房子,最大金额是这两间房子中金额较大的那个
    dp[1] = (nums[0] > nums[1]) ? nums[0] : nums[1];

    // 遍历数组,从第三间房子开始计算
    for (int i = 2; i < numsLen; i++) {
        // 对于第i间房子,有两种选择:
        // 1. 不偷第i间房子,最大金额就是dp[i-1](即前一间房子的最大金额)
        // 2. 偷第i间房子,最大金额是dp[i-2] + nums[i](即前两间房子的最大金额加上当前房子的金额)
        // dp[i]的值是这两种选择中的较大值
        dp[i] = (dp[i - 1] > (dp[i - 2] + nums[i])) ? dp[i - 1] : (dp[i - 2] + nums[i]);
    }

    // 返回dp数组的最后一个值,即所有房子的最大金额
    return dp[numsLen - 1];
}

复杂度分析

  • 时间复杂度:(O(n)),遍历一次数组。
  • 空间复杂度:(O(n)),存储动态规划数组。

打家劫舍(二)

问题描述

沿湖的房间组成一个闭合的圆形,即第一个房间和最后一个房间视为相邻 。给定一个长度为 n 的整数数组 nums,计算在不被发现的前提下最多的偷窃金额。

数据范围:与打家劫舍(一)相同。


示例

输入 输出 说明
[1, 2, 3, 4] 6 偷第 2、4 个房间
[1, 3, 6] 6 偷第 3 个房间

解题思路

环形问题的拆分

由于首尾房间相邻,问题可拆分为两种情况:

  1. 不偷第一个房间 :计算区间 [1, n-1] 的最大偷窃金额。
  2. 不偷最后一个房间 :计算区间 [0, n-2] 的最大偷窃金额。

最终结果为两种情况的最大值。


代码实现

c 复制代码
// 辅助函数:计算从 start 到 end 的最大偷窃金额
int robRange(int* nums, int start, int end) {
    int n = end - start + 1; // 计算从 start 到 end 的区间长度
    if (n == 1) return nums[start]; // 如果区间内只有一间房子,直接返回这间房子的金额

    // 定义动态规划数组 dp,dp[i] 表示从 start 开始到第 i 间房子的最大偷窃金额
    int dp[n];
    dp[0] = nums[start]; // 初始化第一间房子的最大金额
    dp[1] = (nums[start] > nums[start + 1]) ? nums[start] : nums[start + 1]; // 初始化前两间房子的最大金额

    // 遍历从第 3 间房子到最后一间房子
    for (int i = 2; i < n; i++) {
        int current = start + i; // 当前房子在原数组中的索引
        // 状态转移方程:选择当前房子或不选择当前房子的最大金额
        dp[i] = (dp[i - 1] > (dp[i - 2] + nums[current])) ? dp[i - 1] : (dp[i - 2] + nums[current]);
    }

    // 返回从 start 到 end 的最大偷窃金额
    return dp[n - 1];
}
int rob(int* nums, int numsLen) {
    // 如果只有一间房子,直接返回这间房子的金额
    if (numsLen == 1) return nums[0];
    // 如果有两间房子,返回两间房子中金额较大的那个
    if (numsLen == 2) return (nums[0] > nums[1]) ? nums[0] : nums[1];

    // 分两种情况讨论:
    // 1. 不偷最后一间房子,计算从第 0 间到第 (numsLen - 2) 间房子的最大金额
    int case1 = robRange(nums, 0, numsLen - 2);
    // 2. 不偷第一间房子,计算从第 1 间到第 (numsLen - 1) 间房子的最大金额
    int case2 = robRange(nums, 1, numsLen - 1);

    // 返回两种情况中的较大值
    return (case1 > case2) ? case1 : case2;
}

复杂度分析

  • 时间复杂度:(O(n)),两次遍历数组。
  • 空间复杂度:(O(n)),存储动态规划数组。

总结

问题 动态规划状态转移方程 关键点
打家劫舍(一) dp[i] = max(dp[i-1], dp[i-2] + nums[i]) 线性数组,直接遍历
打家劫舍(二) 拆分两种情况求最大值 环形数组,避免首尾同时偷窃
相关推荐
不爱吃炸鸡柳3 分钟前
5道经典贪心算法题详解:从入门到进阶
开发语言·数据结构·c++·算法·贪心算法
枫叶林FYL10 分钟前
【自然语言处理 NLP】8.3 长文本推理评估与针在大海堆任务
人工智能·算法
智者知已应修善业12 分钟前
【51单片机1,左边4个LED灯先闪烁2次后,右边4个LED灯再闪烁2次:2,接着所用灯一起闪烁3次,接着重复步骤1,如此循环。】2023-5-19
c++·经验分享·笔记·算法·51单片机
米啦啦.12 分钟前
红黑树,,
数据结构·红黑树
xiaoye-duck18 分钟前
《算法题讲解指南:优选算法-队列+宽搜》--70.N叉树的层序遍历,71.二叉树的锯齿形层序遍历,72.二叉树的最大宽度,73.在每个树行中找最大值
数据结构·c++·算法·队列
汀、人工智能20 分钟前
[特殊字符] 第98课:数据流中位数
数据结构·算法·数据库架构··数据流·数据流中位数
Eloudy23 分钟前
不同特征值的特征向量互相正交的矩阵
人工智能·算法·机器学习
人道领域25 分钟前
【LeetCode刷题日记】:从 LeetCode 经典题看哈希表的场景化应用---数组、HashSet、HashMap 选型与算法实战
算法·leetcode·面试
努力努力再努力wz26 分钟前
【C++高阶系列】告别内查找局限:基于磁盘 I/O 视角的 B 树深度剖析与 C++ 泛型实现!(附B树实现源码)
java·linux·开发语言·数据结构·c++·b树·算法