当前位置: 首页 > news >正文

LeetCode算法日记 - Day 74: 按摩师、打家劫舍II

目录

1. 按摩师

1.1 题目解析

1.2 解法

1.3 代码实现

2. 打家劫舍II

2.1 题目解析

2.2 解法

2.3 代码实现


1. 按摩师

https://leetcode.cn/problems/the-masseuse-lcci/description/

一个有名的按摩师会收到源源不断的预约请求,每个预约都可以选择接或不接。在每次预约服务之间要有休息时间,因此她不能接受相邻的预约。给定一个预约请求序列,替按摩师找到最优的预约集合(总预约时间最长),返回总的分钟数。

注意:本题相对原题稍作改动

示例 1:

输入: [1,2,3,1]
输出: 4
解释: 选择 1 号预约和 3 号预约,总时长 = 1 + 3 = 4。

示例 2:

输入: [2,7,9,3,1]
输出: 12
解释: 选择 1 号预约、 3 号预约和 5 号预约,总时长 = 2 + 9 + 1 = 12。

示例 3:

输入: [2,1,4,5,3,1,1,3]
输出: 12
解释: 选择 1 号预约、 3 号预约、 5 号预约和 8 号预约,总时长 = 2 + 4 + 3 + 3 = 12。

1.1 题目解析

题目本质
这是一个多状态 dp 问题,核心是在数组中选择若干不相邻的元素,使其和最大。

常规解法
最直观的想法是枚举所有可能的选择方案,对于每个预约,尝试"选"和"不选"两种情况,然后比较所有合法方案(不包含相邻预约)的总时长,取最大值。

问题分析
暴力枚举需要遍历所有 2^n 种子集组合,再判断是否有相邻元素,时间复杂度达到 O(2^n),当预约数量稍大时就会超时。问题的关键在于存在大量重复计算——对于位置 i 的决策,后续子问题会被重复求解多次。

思路转折
 要想高效求解 → 必须避免重复计算 → 使用动态规划记录状态。观察发现,对于第 i 个预约,我们只需要知道"前 i-1 个预约中,最后一个是否被选择"这个状态,就能推导出当前的最优解。因此定义两个状态:

  • f[i]:前 i 个预约中,选择第 i 个预约的最大时长

  • g[i]:前 i 个预约中,不选第 i 个预约的最大时长

这样可以通过前一个位置的状态递推得到当前状态,时间复杂度降至 O(n)。

1.2 解法

算法思想: 动态规划,维护两个状态数组。状态转移方程为:

  • f[i] = g[i-1] + nums[i](选第 i 个,则第 i-1 个必不选)

  • g[i] = max(f[i-1], g[i-1])(不选第 i 个,取前一个选或不选的最大值)

最终答案为 max(f[n-1], g[n-1])。

i)初始化两个数组 f 和 g,长度为预约数量

ii)处理边界:当数组为空时返回 0,否则设置 f[0] = nums[0](选第一个),g[0] = 0(不选第一个)

iii)从位置 1 开始遍历数组,依次计算每个位置的 f[i] 和 g[i]

iv)返回最后一个位置选或不选的最大值

易错点

  • 边界条件处理:需要单独判断数组为空的情况,以及初始化 f[0] 和 g[0]

  • 状态转移理解:f[i] 表示"必选第 i 个",所以只能从 g[i-1] 转移;而 g[i] 表示"不选第 i 个",可以从 f[i-1] 和 g[i-1] 中取最大值

  • 最终结果:需要比较 f[m-1] 和 g[m-1] 取最大值,而不是只返回其中一个

1.3 代码实现

class Solution {int[] f;  // f[i]: 选择第i个预约的最大时长int[] g;  // g[i]: 不选第i个预约的最大时长public int massage(int[] nums) {int m = nums.length;if(m == 0) return 0;f = new int[m];g = new int[m];// 初始化f[0] = nums[0];g[0] = 0;// 状态转移for(int i = 1; i < m; i++){f[i] = g[i-1] + nums[i];  // 选第i个,加上前一个不选的最优值g[i] = Math.max(f[i-1], g[i-1]);  // 不选第i个,取前一个的最优值}return Math.max(f[m-1], g[m-1]);}
}

复杂度分析

  • 时间复杂度: O(n),只需遍历一次数组,每个位置的状态转移为 O(1)

  • 空间复杂度: O(n),需要两个长度为 n 的数组存储状态。可优化至 O(1),因为每次只需要前一个位置的状态,用变量滚动即可

2. 打家劫舍II

https://leetcode.cn/problems/house-robber-ii/description/

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。

示例 1:

输入:nums = [2,3,2]
输出:3
解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。

示例 2:

输入:nums = [1,2,3,1]
输出:4
解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。偷窃到的最高金额 = 1 + 3 = 4 。

示例 3:

输入:nums = [1,2,3]
输出:3

提示:

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 1000

2.1 题目解析

题目本质
本质上是"带环形约束的序列最优选择"问题,换句话说就是"一圈房子围成环,不能偷挨着的两家,怎么偷能拿最多钱"核心难点在于首尾相邻的特殊处理。

常规解法
最直观的想法是用递归/回溯:枚举所有不相邻的房屋组合,计算每种组合的总金额,取最大值。但需要额外处理首尾不能同时选的约束。

问题分析
纯递归会超时。对于 100 个房屋,需要枚举的组合数接近 O(2^100),且存在大量重复计算。关键问题是环形结构:普通的线性 DP 无法直接应用,因为第一间和最后一间相邻,选了第一间就不能选最后一间。

思路转折
要想高效 → 必须用动态规划 → 但要破环为链。关键观察:首尾相邻等价于"第一间和最后一间不能同时选"。可以分两种情况:① 偷第一间(不能偷最后一间),② 不偷第一间(可以偷最后一间)。每种情况都变成了线性 DP(打家劫舍 I),分别求最大值,再取两者的较大值。这样就把环形问题转化为两个线性问题。

2.2 解法

算法思想

破环为链,分两种情况分别用线性 DP 求解,取最大值。

情况1:偷第一间房 → 最后一间不能偷 → DP 范围:[0, n-2]

情况2:不偷第一间房 → 最后一间可以偷 → DP 范围:[1, n-1]

线性 DP 状态定义:

  • f[i]:偷第 i 间房的最大金额

  • g[i]:不偷第 i 间房的最大金额

递推公式:

f[i] = g[i-1] + nums[i]         // 偷i,不能偷i-1g[i] = max(f[i-1], g[i-1])      // 不偷i,前面随便选

i)

  1. 边界处理:n=0 返回 0,n=1 返回 nums[0],n=2 返回 max(nums[0], nums[1])

ii)调用线性 DP 求解两种情况:

  • 返回两种情况的较大值

  • 情况2:rob1(nums, 1, n-1) 处理 [1, n-1] 区间

  • 情况1:rob1(nums, 0, n-2) 处理 [0, n-2] 区间

iii)返回两种情况的较大值

iv)rob1 方法实现:

  • 初始化起点:f[left] = nums[left], g[left] = 0
  • 从 left+1 遍历到 right,递推更新 f 和 g
  • 返回 max(f[right], g[right])

易错点

  • 边界判断不全:n=1 和 n=2 需要特殊处理。当 n=1 时只有一间房,直接偷;当 n=2 时两间相邻,只能偷其中较大的

  • 区间范围错误:情况1 的范围是 [0, n-2](包含第一间),情况2 的范围是 [1, n-1](包含最后一间)。容易写反或遗漏边界

  • 初始化混乱:rob1 方法中 f[left] = nums[left] 是初始化起点,从 left+1 开始循环。不要从 left 开始循环导致重复初始化

  • 返回值选择错误:最终返回 max(f[right], g[right]),因为最后一间可能偷也可能不偷,要取最优的

2.3 代码实现

static class Solution {int n;public int rob(int[] nums) {n = nums.length;if (n == 0) return 0;else if (n == 1) return nums[0];else if (n == 2) return nums[0] > nums[1] ? nums[0] : nums[1];// 情况1:考虑第一间,不考虑最后一间,范围 [0, n-2]// 情况2:不考虑第一间,考虑最后一间,范围 [1, n-1]return Math.max(rob1(nums, 0, n - 2), rob1(nums, 1, n - 1));}// 线性打家劫舍(处理 [left, right] 范围)public int rob1(int[] nums, int left, int right) {int[] f = new int[n];  // 偷第 i 间房的最大金额int[] g = new int[n];  // 不偷第 i 间房的最大金额// 初始化起点f[left] = nums[left];g[left] = 0;// 从 left+1 遍历到 rightfor (int i = left + 1; i <= right; i++) {f[i] = g[i - 1] + nums[i];      // 偷i = 不偷i-1的最优 + nums[i]g[i] = Math.max(f[i - 1], g[i - 1]);  // 不偷i = 前面的最优}// 返回最后位置偷或不偷的最大值return g[right] > f[right] ? g[right] : f[right];}
}

复杂度分析

  • 时间复杂度:O(n),两次线性遍历,每次约 n 个元素

  • 空间复杂度:O(n),使用了两组长度为 n 的数组(f, g 和 f1, g1 在不同调用中复用)

http://www.dtcms.com/a/490699.html

相关文章:

  • centos离线包获取-附centos7主流离线包资源
  • 电子商务网站建设哪好网站内链建设
  • 网站建设的主题软媒win7优化大师
  • 人力网站建设的建议软文平台发布
  • 【35】MFC入门到精通——MFC运行 不显示对话框 MFC界面不显示
  • 开源 C++ QT QML 开发(二十一)多媒体--视频播放
  • PMBT2222A,215 开关晶体管功率二极管 NXP安世半导体 音频放大电路 LED驱动 应用
  • 大语言模型(LLM)入门笔记:嵌入向量与位置信息
  • 网站设计济南做网站的一定要开80或8080端口
  • 【Spring Boot从入门到精通】原理、实战与最佳实践
  • uni-app 入门学习教程,从入门到精通,uni-app 基础知识详解 (2)
  • Pyspark分布式访问NebulaGraph图数据库
  • FPGA----petalinux的Ubuntu文件系统移植
  • 宜昌网站建设厂家wordpress 扁担
  • TensorFlow2 Python深度学习 - 卷积神经网络示例2-使用Fashion MNIST识别时装示例
  • Eureka: Human-Level Reward Design via Coding Large Language Models 译读笔记
  • 随时随地看监控:我的UptimeKuma远程访问改造记
  • 关于网站篡改应急演练剧本编写(模拟真实场景)
  • 河北省企业网站建设公司企业管理系统软件有哪些
  • JVM的classpath
  • RVO优化
  • ethercat 环型拓扑(Ring Topology)
  • 颠覆PD快充、工业控制与智能家电等领域高CTR,高隔离电压高可靠性光电耦合器OCT1018/OCT1019
  • 【机器学习入门】8.1 降维的概念和意义:一文读懂降维的概念与意义 —— 从 “维度灾难” 到低维嵌入
  • 黄骅市旅游景点有哪些盐城网站关键词优化
  • 对于网站建设的调查问卷爱南宁app官网下载
  • 一文读懂 YOLOv1 与 YOLOv2:目标检测领域的早期里程碑
  • 在 Windows 10/11 LTSC等精简系统中安装Winget和微软应用商店,Windows Server安装Microsoft Store的应用
  • A2A架构详解
  • 基础 - SQL命令速查