角色每秒先受伤后回血,可开启一段连续无敌且不能回血的技能,判断能否存活到战斗结束。

考查重点:时序生存约束与技能窗口。每秒先查受伤后是否仍活,再回血;枚举技能窗口,用前缀和与最低血量信息验证技能前后,不能只看终值

本题目录

参考答案

核心回答

这道题不能只检查战斗结束时的血量,也不能只把每秒合并成“回血减伤害”:角色在当秒受伤后若血量已经不大于 0,就没有机会再接受回血。 可以枚举技能开始时间,用净变化前缀和,以及受伤后最低血量的前缀、后缀最小值,把每个方案的检查降为 O(1),总时间 O(n)。

下面按这些条件实现:初始血量为 m,伤害与回血均非负,没有额外的血量上限;技能最多开一次,从某秒受伤前开始,连续 k 秒同时跳过伤害和回血,血量全程严格大于 0。技能持续到战斗结束之后的部分不影响结果。函数返回布尔值,需要输出时再对应成 yes 或 no。

把完整过程转成两个量

用从 0 开始的下标,定义净变化前缀和:

P[0] = 0
P[i+1] = P[i] + b[i] - a[i]

不使用技能时,第 i 秒受伤后的血量为:

low[i] = m + P[i] - a[i]

因为随后回血 b[i] >= 0,只要所有受伤后的血量都大于 0,该段就能存活。即使不使用技能的过程会死亡,也可以把 P[i]、low[i] 当成代数基准预先计算;实际可行性由后面的条件检查。

枚举无敌窗口如何检查

假设跳过左闭右开区间 [left, right),其中 right = left + k。这段原本的净变化为 skipped_delta = P[right] - P[left]。跳过后:

  • 窗口之前,血量与原过程相同,要求 pre_min[left] > 0。
  • 窗口之内,伤害与回血都不发生,血量保持在进入窗口时的正值。
  • 窗口之后,所有血量相对原过程统一减少 skipped_delta,要求 suf_min[right] - skipped_delta > 0。

预处理前缀最小值、后缀最小值后,这两项都能 O(1) 检查。空前缀或空后缀的最小值记为正无穷。

注意 skipped_delta 可以为正。跳过一段净回血区间,会让后续血量更低,所以不能一律把无敌看成增加血量。

当 k >= n 时,从战斗开始覆盖全程即可。若 k < n,只枚举完整落在战斗中的长度 k 窗口也足够:任何向战斗结束后延伸的可行窗口,都可以提前到 [n-k, n),角色在更早的开始时刻仍活着,此后一直无敌直到结束。

Python 实现

def can_survive(m, damage, heal, k):
    n = len(damage)
    if len(heal) != n or k < 0:
        raise ValueError("invalid lengths or duration")
    if any(x < 0 for x in damage) or any(x < 0 for x in heal):
        raise ValueError("damage and heal must be nonnegative")
    if m <= 0:
        return False
    if n == 0 or k >= n:
        return True

    prefix = [0] * (n + 1)
    low = [0] * n
    for i in range(n):
        low[i] = m + prefix[i] - damage[i]
        prefix[i + 1] = prefix[i] + heal[i] - damage[i]

    inf = float("inf")
    pre_min = [inf] * (n + 1)
    suf_min = [inf] * (n + 1)
    for i in range(n):
        pre_min[i + 1] = min(pre_min[i], low[i])
    for i in range(n - 1, -1, -1):
        suf_min[i] = min(suf_min[i + 1], low[i])

    if pre_min[n] > 0:
        return True
    for left in range(n - k + 1):
        right = left + k
        skipped_delta = prefix[right] - prefix[left]
        if pre_min[left] > 0 and suf_min[right] - skipped_delta > 0:
            return True
    return False

前缀和、两侧最小值和窗口枚举都只各遍历常数次,时间复杂度 O(n),额外空间 O(n)。k=0 等价于没有技能;空战斗和非正初始血量也单独处理。

用例子检查先后顺序

初始血量 3,伤害为 [4, 1, 5],回血为 [0, 10, 0],技能持续 1 秒。若在第一秒开启技能,血量保持 3;第二秒先降为 2,再恢复到 12;第三秒降为 7,可以存活。若只盯着最大的伤害 5,把技能留到第三秒,第一秒已经死亡。

把上述三秒按“受伤、判生死、回血”的次序展开:

第一秒开技能可活到最后,若把技能留给第三秒则第一秒已死亡

这里只比较正文给出的两个技能时机。受伤后不大于 0 就终止,后面的回血不能挽救本秒已死亡的角色;算法仍需检查所有候选窗口。

再看初始血量 5、当秒伤害 5、随后回血 100,且没有技能的情况:受伤后恰好为 0,应判定死亡,不能因为最终净变化为正而通过。

本解法依赖血量变化可以线性相加。若增加最大血量、技能期间仍可回血、技能次数或触发时机等条件,需要重新推导状态,不能直接沿用“后缀统一平移”的公式。

参考资料

相关面经

2 篇

去哪儿

AI 应用 / 全栈
查看原面经 →
玩家初始有m滴血,第i秒收到来自怪兽的a[i]点伤害,随后会接受来自法师的b[i]点回血。玩家还有一项技能,可以在k秒内无法选中,既无法收到伤害,也无法接受回血。总共有n秒战斗,询问战斗结束时是否能够有存活的机会,能则输出yes不然是no。(数据:均小于1e4)

去哪儿

AI 全栈开发工程师
校招
查看原面经 →
玩家初始有x滴血,第i秒收到来自怪兽的a[i]点伤害,随后会接受来自法师的b[i]点回血。玩家还有一项技能,可以在y秒内无敌,使出技能的这段时间内,既无法收到伤害,也无法接受回血。总共有n秒战斗,询问战斗结束时是否能够有存活的机会。

相关题目

4 题