leetcode3499 夜探丹炉得天机,白嫖三剑破死局

夜探丹炉得天机,白嫖三剑破死局


第一章:夜半飞书领死命

我叫令码冲,青云门的外门弟子,专职炼丹——说得好听叫“自动化丹药流程工程师”,说白了就是每天对着丹炉跑批处理,写写 Python 脚本,偶尔给师兄们 debug 一下炼丹炉的固件漏洞。

今夜子时,我正盘腿在蒲团上吐纳,面前的炼丹炉屏幕蓝光幽幽,上面滚动着一行行三年前的祖传代码,注释里赫然写着 # 能跑就别动

忽然,腰间令牌嗡嗡作响,一道金光破空而来——是掌门飞鸽传书!我点开一看,差点真气逆行:

“令码冲,明日卯时前,需将这批丹药的’连续极品丹串’最大化。若成,赏你《算法导论》残卷一页;若败…去扫厕所三年。”

我手一抖,令牌差点掉地上。《算法导论》残卷!那可是每个程…不对,每个炼丹师的至高心法!据说参透之人,可以 O(n) 时间复杂度渡劫飞升!而厕所三年……我可不想手持扫帚,在厕所里思考人生。

我打开丹炉监控面板,只见丹药状态如下:

丹炉状态: 0 — 1 — 0 — 1 — 0
         ↑       ↑       ↑
        三颗废丹,导致整炉丹药品阶上不去

我倒吸一口凉气。五颗丹药,三颗废丹两颗极品丹,甚至连个串都连不起来。


第二章:扫地僧人破天机

我赶紧翻开《青云丹道宝典》,在三万七千二百一十八条注释里,找到了那条传说中的天道规则。

第 37218 条·逆天改命术

施展次数限制:仅一次(别问为什么,问就是天道服务器限流,单用户 QPS = 1)

前置条件:丹药序列两侧自动附加隐形极品丹结界(即 '1' + s + '1'),助你一臂之力 。注:结界不计入最终产量,纯属工具人属性。

操作步骤

  1. 捏碎一片“被废丹包围”的极品丹区块——注意,是整片,一颗不留
  2. 换取把一片“被极品丹包围”的废丹全炼成极品

捏碎一片极品丹,亏损那片极品丹的总数量。炼化一堆废丹,收益等于那堆废丹的总数量。只要赚的比亏的多,这买卖就划得来。

我再次看向丹炉里的 0-1-0-1-0,试着选中右边那颗极品丹——也就是第二个 1。它左边是 0,右边也是 0,完美符合条件一“被废丹包围”。第一步,捏碎。0-1-0-1-0 变成了 0-1-0-0-0

第二步,判定。加上两侧的隐形结界,序列变成:(1)-0-1-0-0-0-(1) 。这三颗连成一片的废丹,左边是 1,右边也是 1——被极品丹包围,条件二满足!一口气全炼成极品。结界内的真实丹药变成了:0-1-1-1-1

我愣了整整三息。然后盯着那颗本该被我捏碎的丹——它好端端地待在那里,金光闪闪,仿佛什么都没发生过。

一片极品丹的“虚拟团灭”,换了两堆废丹的“赛博飞升”。

隔壁工位的师兄任码行正趴在丹炉前打盹,被我吵醒后眯着眼看了一会儿,忽然冒出一句:“这不就是……量子纠缠炼丹法?那颗丹处于既被粉碎又没被粉碎的叠加态,直到你观测它的时候——坍缩成了极品。”

“得了吧,”我头也没回,“你上个月用这个理由解释自己写的死循环,掌门差点把你发配去喂灵兽。”

任码行讪讪地缩回脑袋,继续打盹。

但他说的话有一半是对的。那颗丹确实经历了一段“薛定谔的死亡”——在步骤一和步骤二之间,它短暂地变成了废丹,短暂到连日志都来不及记录。

就在我仔细琢磨这到底算炼丹还是卡 bug 时,身后突然传来一个幽幽的声音——“施主,你这不是炼丹,你这是卡了天道的 off-by-one 。”

我回头一看,是扫地僧——门派里最神秘的存在。据说年轻时也是个炼丹师,写过一手好 SQL,后来因为一次实验疏忽,把所有丹炉都炸了,被掌门的罚扫一百年厕所!

我连忙请教:“前辈,何谓 off-by-one ?”

扫地僧放下扫帚,缓缓说道:“步骤一,捏碎一整片被废丹包围的极品丹。表面看,你亏掉了整片极品丹,但关键在于——这片极品丹全部变成了废丹后,刚好填上了左右两堆废丹之间的缺口——左右两堆废丹因为中间这片薛定谔丹的加入,三者合一,连成了一整片巨大的废丹区域。”

“步骤二,天道检测到这一大片废丹左右都是极品结界,于是大手一挥,整片炼化。注意,是整片,包括那片刚刚被你捏碎、暂时变成废丹的极品区域。”

他抬头看着我,眼神里带着一丝前辈对后辈的慈祥:“整个过程,那片极品丹在步骤一里短暂地变成了 0,然后在步骤二里立刻被炼回 1。就像一批临时变量,赋值完就被覆盖,只活在寄存器里,连内存都没落盘,但恰恰改变了边界判定,触发了步骤二的炼化条件。”

“这就叫 off-by-one。边界偏移一点,满盘皆活。天道只校验最终结果,不审计中间过程。在炼丹界,这叫逆天改命;在编程界——叫卡 bug 过单测。”

我醍醐灌顶。


第三章:白嫖三剑定乾坤

扫地僧说完,拎起扫帚继续去拖他的地,留下我一个人对着丹炉屏幕发呆。

天道规则的核心漏洞在于——步骤一里捏碎的那片极品丹,会在步骤二里跟着两边的废丹一起被炼回来。整片极品丹从 1 短暂变成 0,充当了左右废丹堆的粘合剂,然后立刻被拉回 1 的队伍。净效果就是:极品丹总数不变,两边的废丹白嫖成极品。

那么最终答案就呼之欲出了:

最大极品丹数 = 原本所有极品丹的总数 + 通过一次操作能白嫖到的最大废丹数

原本的极品丹总数很好办,遍历一遍,见到 1 就累加,完事。真正的问题在于:怎么找到“白嫖废丹数”的最大值?

假设我选中一片被废丹包围的极品丹区块,它的左边有一堆废丹,右边也有一堆废丹。施展逆天改命术后,这三部分会合并成一整片废丹,然后被整体炼化成极品。白嫖到的废丹,就是左边废丹堆 + 右边废丹堆的总长度。祭品区本身先死后活、纯属工具丹。

由此,问题转化为: 在序列中找到两个被至少一堆极品丹隔开的相邻废丹堆,长度之和最大

思路清晰了,三步收工:

第一,把丹药序列按连续同类分组,分清哪些是废丹堆、哪些是极品堆。 第二,遍历这些分组,对每一个废丹堆,把它和“上一个废丹堆”配成一对,计算长度之和,更新最大值。 第三,把原本的极品丹总数和最大白嫖数一加,收工。

第一式·起手式:groupby 分拣术

用什么来分组?Python 标准库里的 itertools.groupby——这玩意儿天生就是干这个的。把丹药序列丢进去,它自动按连续同类分组打包,每一包自带长度属性,连手写状态机都省了,妥妥的“丹药自动分拣流水线”。"001100" 进去,出来就是 ["00", "11", "00"] 三大坨。每坨自带标签——'0' 是废丹组,'1' 是极品组。

第二式·破剑式:pre0 记前任

pre0 记录上一个废丹堆的长度。初始值 -inf 是精髓中的精髓——第一堆废丹没有前任,如果初始化为 0,就会无中生有个“长度为 0 的虚拟废丹堆”,和第一堆废丹配对,产生虚空白嫖的幻觉。用 -inf 则永无翻身之日,任何正数加它还是负无穷,max 永远不会选它。当第二个废丹堆出现时,pre0 已经被更新为第一个废丹堆的真实长度,这时配对才有意义——这是我从张无码师兄那里学来的,他说这叫"虚无初始化"——给不存在的东西一个负无穷,让它永远无法成为最优解。

第三式·总诀式:total1 + mx 一剑封喉

total1 是家底,mx 是外快。家底一分不少,外快能捞多少捞多少。两者相加,就是最终答案。

from itertools import groupby

def maxActiveSectionsAfterTrade(s: str) -> int:
    total1 = 0          				# 所有极品丹的总数,一颗都不会少
    mx = 0               				# 相邻废丹堆长度之和的最大值(最大白嫖数)
    pre0 = float('-inf') 				# 上一个废丹堆的长度,初始“虚无”

    for char, group in groupby(s):
        length = sum(1 for _ in group)  
        if char == '1':
            total1 += length            # 极品丹,一颗不落全入账
        else:  # char == '0'
            if pre0 != float('-inf'):   # 前面有过废丹堆,可以配对
                mx = max(mx, pre0 + length)
            pre0 = length               # 当前废丹堆成为下一对的前任

    return total1 + mx

一次遍历,时间复杂度 O(n),空间复杂度 O(1)。任码行凑过来看了一眼,酸溜溜地嘀咕:“这不就是贪心吗……”

“贪心?”我一脸正色地看着他,“任码行,你摸着良心说,独孤九剑是不是也只用一个循环遍历敌人的破绽?我这心法遍历废丹堆找最大白嫖对,本质上是同一种东西——都叫‘一剑封喉,绝不回头’。”

任码行张了张嘴,没有反驳。


第四章 残卷六字解真意

心法既成,是骡子是马,得拉进丹炉遛遛。

我剑指丹炉里那条 0-1-0-1-0 ,丹炉轰鸣,四道金光冲天而起,在空中连成一条龙形丹气,盘旋三圈,才缓缓消散。控制台弹出结果:4。四颗极品丹,比原本多了整整一倍!

我瘫倒在蒲团上,望着天花板傻笑。曾经有一片极品丹摆在我面前,我把它们捏碎了。等我炼化完废丹才发现,它们又回来了。人世间最爽的事莫过于此。如果上天再给我一次机会,我会对那片极品丹说三个字:工具丹。如果非要在这份白嫖上加一个上限,我希望是——一万颗。

窗外天边泛起鱼肚白。卯时将至,我将结果打包成飞鸽传书,发往掌门大殿。

片刻后,一道金光落入掌心,正是《算法导论》残卷一页。翻开,上面只写了五个大字:

“考虑边界条件。”

知识共享许可协议
本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可。