☰
LeetCode 495: 提莫攻击 (模拟) —— 题解
2026/10/10 2:46:18 网站建设 项目流程

👋 欢迎阅读

🎯 欢迎来到「提莫攻击」题解之旅!本文将带你从"计算英雄被连续攻击后的总中毒时间"这一直观场景出发,深入理解区间合并 + 贪心累加的巧妙运用,并掌握如何比较相邻攻击间隔与中毒时长来累加不重叠的总时长。

在开始之前,建议你先:

  • 了解题目背景:这是 LeetCode 495 题,给定非递减的攻击时间数组timeSeries和中毒持续时长duration,求敌人总中毒时间。本质上,每次攻击产生一个长度为duration的区间,重叠部分只算一次,问题转化为合并区间求总长度。

  • 明确学习目标:掌握相邻区间重叠判断技术,理解间隔 >= duration与< duration两种情况的累加差异,并熟练处理单次攻击与时间可能重复等边界情况。

  • 准备好环境:建议在本地 IDE 或 LeetCode 在线编辑器中打开代码,边看边运行,亲手验证示例(如timeSeries = [1,4], duration = 2输出4,timeSeries = [1,2], duration = 2输出3)。

本文将从问题转化、重叠判断、贪心累加、边界防护到代码实现,层层递进。即使你对区间合并还不熟悉,我们也会从"两次中毒叠在一起就只算一次"这一直觉出发,让你轻松抓住核心思想——重叠只补差,不重叠算满。现在,让我们一起累加中毒时间,算出总时长吧! ⏱️🎯

🔥愿旖旎·个人主页

📘学习专栏:《算法专栏》《LangChain学习》《贪心算法》

🌄 钱塘江上潮信来,今日方知我是我

✨当前学习内容:《模拟》


一.题目

495. 提莫攻击 - 力扣(LeetCode)

​

二、算法分析

一、问题分析(前置分析)

  • 题目要求:给定非递减的攻击时刻数组timeSeries与中毒时长duration,求敌人处于中毒状态的总时长。
  • 关键约束:攻击时间非递减(可能相同);中毒区间重叠部分只计一次;时长可能超出 int(累加需注意)。
  • 核心思路:把每次攻击看作[t, t+duration)的时间区间,问题即求这些区间的并集总长度。由于时间有序,只需顺序比较相邻两次攻击的间隔:间隔足够大则加满duration,否则只加上间隔(重叠部分不重复计)。

📌 例子:为什么"重叠只算一次"

timeSeries = [1, 2],duration = 2:第 1 次攻击中毒[1, 3),第 2 次中毒[2, 4),两者在[2, 3)重叠 1 秒。总时长若简单相加是 2+2=4,但[2,3)被算了两次;正确答案是并集长度[1,4) = 3——重叠部分必须只算一次,这就是"重叠只补差"的由来。

二、算法策略(区间合并 · 相邻间隔比较)

核心步骤:

  1. 初始化:sum = duration(第一次攻击必然完整贡献一个 duration)。
  2. 遍历后续攻击:i从 1 到n-1,计算相邻间隔gap = timeSeries[i] - timeSeries[i-1]。
  3. 判断重叠:
    • gap >= duration→ 两次中毒完全不重叠,本次贡献完整duration;
    • gap < duration→ 两次中毒重叠duration - gap,本次只贡献gap(重叠部分已计入上次)。
  4. 累加并返回:sum += gap或sum += duration,遍历结束返回sum。

📊 示例(timeSeries = [1, 4],duration = 2,期待4):

步骤当前攻击上次攻击gap与 duration(2) 比较累加sum
初始化1———首次完整时长2
i=14133 >= 2(不重叠)+ duration = +24✅

两次中毒[1,3)、[4,6)完全不重叠,总时长2 + 2 = 4,与题目示例一致。

再看重叠情形(timeSeries = [1, 2],duration = 2):

步骤当前上次gap判断累加sum
初始化1———首次2
i=12111 < 2(重叠)+ gap = +13✅

重叠部分[2,3)不重复计,总时长3,正确。

三、正确性说明(简单版本)

  • 区间并集等价:每次攻击对应区间[t, t+duration),总中毒时间 = 这些区间的并集长度。相邻两次攻击的并集长度 =min(gap, duration) + duration(被前一次覆盖的部分不重复算),逐对合并即得全局并集长度。
  • 两种情况覆盖完备:gap >= duration与gap < duration是互斥且完备的两类(间隔要么够大要么不够),分别加duration与gap,无遗漏分支。
  • 有序性保证正确:时间非递减使区间按起点有序,因此只需比较相邻区间即可完成合并——非相邻区间不可能产生新的重叠(若i与i+2重叠,则i+1必与两者都重叠,已被逐步吸收),不会漏算重叠。
  • 无重复无遗漏:每个区间的"新增贡献"恰好是"它超出上一区间末尾的部分",逐次累加即得并集总长,不重不漏。

📌 例子:为什么有序性让"只看相邻"就足够

timeSeries = [1, 2, 3],duration = 5:区间[1,6)、[2,7)、[3,8)。只看相邻:gap(1,2)=1 < 5加 1、gap(2,3)=1 < 5加 1,总长5+1+1 = 7,正是并集[1,8)的长度 ✅。若无需有序、区间乱序,就必须先排序再合并;题目给出非递减正是为了免去排序,使 O(n) 一遍扫描成为可能。

四、实现细节(边界防护)

  • 初始化:n = timeSeries.size()、sum = duration(关键:循环从i = 1开始,首次攻击的时长必须预先计入)。
  • 边界防护:n == 1(单次攻击)时循环不进入,直接返回duration✅;n == 0时(题目保证非空,但严谨可加if (n == 0) return 0;);时间可能重复(gap = 0 < duration)时加 0,即完全重叠不增加时长,逻辑自然正确。
  • 复杂度:时间 O(n)(单次遍历),空间 O(1)(仅常数个变量)。
  • 关键判断:if (timeSeries[i] - timeSeries[i - 1] >= duration) sum += duration; else sum += timeSeries[i] - timeSeries[i - 1];(重叠判断与累加)。

📌 例子:单次攻击与时间重复的边界

timeSeries = [5]、duration = 3:n=1,循环不执行,返回初始化的3✅(一次攻击完整中毒 3 秒);timeSeries = [1, 1]、duration = 2:gap = 0 < 2,sum += 0,返回2✅(同一时刻攻击两次,中毒区间完全重合,只算一次)。

五、返回值(目标映射)

  • 返回sum:敌人处于中毒状态的总秒数,对应题目"返回总中毒时间"。

三.代码

class Solution { public: int findPoisonedDuration(vector<int>& timeSeries, int duration) { int n = timeSeries.size(); int sum = duration; // 第一次攻击必然完整贡献一个 duration // 1. 顺序比较相邻两次攻击的间隔 for (int i = 1; i < n; i++) { if (timeSeries[i] - timeSeries[i - 1] >= duration) { // 间隔够大:两次中毒完全不重叠,本次贡献完整时长 sum += duration; } else { // 间隔不足:两次中毒重叠,只加上“超出上次末尾”的那部分 sum += timeSeries[i] - timeSeries[i - 1]; } } return sum; // 2. 返回总中毒时间 } };

四、易错点分析

难点1:初始值必须是duration,循环从i = 1开始

int sum = duration; // 首次攻击预先计入 for (int i = 1; i < n; i++) // 从第二次攻击开始

首次攻击的[t0, t0+duration)没有任何前驱区间可以重叠,必然贡献完整duration。若把sum初始化为 0 且循环仍从i = 1开始,会永远漏掉第一次攻击的时长;若循环从i = 0开始并比较timeSeries[-1],则越界。"初始值 = duration + 循环从 1 开始"是本题的固定搭配。

难点2:重叠时为什么加gap而不是gap + duration或duration - gap

sum += timeSeries[i] - timeSeries[i - 1]; // 只加间隔

本次中毒区间是[t_i, t_i + duration),其中[t_i, t_{i-1} + duration)这一段已被上一次攻击覆盖(因为gap < duration),只有超出部分才是新增。超出长度 =(t_i + duration) - (t_{i-1} + duration) = t_i - t_{i-1} = gap。若加成duration会把重叠部分重复计算;若加成duration - gap则少算——"重叠只补差"的差值正是gap,这是本解法最核心的一步推导。

难点3:为什么只需要比较"相邻"两次攻击

if (timeSeries[i] - timeSeries[i - 1] >= duration)

时间数组非递减(题目保证),区间按起点有序。有序区间合并的性质:任意区间只可能与"紧挨着的前一个区间"产生新的重叠——若它与更早的区间重叠,那么中间那些区间早已把它们连成一片,重叠已被逐步吸收。因此一遍相邻比较就等价于完整合并,无需双重循环或排序。这是"有序性"带来的关键简化。

难点4:边界退化情形(单次攻击 / 时间重复)

// n == 1:循环不进入 → 返回 duration // timeSeries = [1,1]:gap = 0 → sum += 0

n == 1时只执行一次初始化,返回duration,天然正确;攻击时间可能相同(gap = 0 < duration),此时两次中毒完全重合,加 0 意味着不增加时长,逻辑自洽。这两个边界无需特判,都由统一的判断分支自然处理。

五、流程图

🎯 闭幕

🎉 恭喜你完成了「提莫攻击」问题的学习!

为了巩固知识并进一步拓展,建议你:

🚀动手实践
在 LeetCode 上提交代码,尝试不同的测试用例。

💡深入思考

  • 代码用sum = duration初始化,表示第一次攻击必然完整贡献一个duration。为什么第一次攻击不会与之前的攻击重叠?如果攻击时间序列为空,代码会返回什么?

  • 循环中比较相邻两次攻击的时间差timeSeries[i] - timeSeries[i-1]与duration。当时间差 ≥ duration 时,为什么直接加duration?当时间差 < duration 时,为什么只加时间差?这体现了怎样的重叠处理逻辑?

  • 代码只比较了相邻两次攻击,而没有考虑更早的攻击。为什么相邻比较就足够?如果存在多次攻击连续重叠,这种累加方式是否仍然正确?请举例验证。

如果你觉得本文对你有所帮助,欢迎:

👍点赞 / 收藏
👤关注作者,获取更多题解
💬留言交流你的疑问或优化思路


📌深入思考答案

  • 第一次攻击不会与之前重叠,因为它是序列中的第一个,之前没有任何攻击。如果timeSeries为空,n = 0,代码中sum = duration会执行吗?实际上int sum = duration;在循环前,但若n = 0,timeSeries为空,duration可能无意义;不过题目通常保证n ≥ 1,若为空则需特殊处理返回 0。

  • 时间差 ≥ duration说明两次攻击的中毒区间完全不重叠,因此第二次攻击贡献完整的duration;时间差 < duration说明第二次攻击的中毒区间与第一次有重叠,总中毒时间只增加“超出上次中毒结束”的部分,即时间差。这体现了区间并集的思想。

  • 相邻比较足够,因为每次攻击的中毒区间只可能与前一次攻击的中毒区间重叠,更早的攻击若与当前重叠,必然也经过了中间的某次攻击,且区间并集的累加是逐步进行的。例如攻击时间[1,2,3],duration=5,相邻时间差均为 1 < 5,累加 1+1=2,再加上初始 5,总时长 7,实际并集为[1, 8)长度 7,正确。

祝你在算法之路上越走越稳,早日攻克每一道难题!下次见 🚀✨

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询