- 示例工程
【免费下载链接】leetcode
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
导读
LCP 49. 环形闯关游戏(力扣题库编号K8GULz)是「力扣挑战赛」系列中一道标记为困难的经典构造题,考察对位运算(OR)、贪心扩展与循环数组边界的综合运用。本文以 lcp/LCP 49. 环形闯关游戏/README.md 的题面与示例为骨架,逐条拆解闯关规则背后的数学结构,推导「最小初始积分」的正确求解方法,并给出经随机暴力校验的贪心闭包实现,以及一组自建的验证用例。读完本文,你将掌握环形区间扩张问题的建模思路、位运算累加技巧,以及为什么这类问题不能直接用二分答案。
题目重述与规则拆解
「力扣挑战赛」中有一个由N个关卡组成的环形闯关游戏,关卡编号为0 ~ N-1,其中编号0与编号N-1相邻。challenge[i]表示挑战编号i的关卡最少需要拥有的积分。
环形闯关游戏关卡示意图{:width="240px"}
闯关规则共三条:
- 初始开启:小扣可以指定其中一个关卡为「开启」状态,其余关卡处于「未开启」状态。注意题目问的是初始最少需要多少积分,而初始开启的关卡可以自由选择;
- 挑战条件与积分增长:小扣可以挑战处于「开启」状态且满足最少积分要求的关卡;若挑战前积分为
score,挑战结束后积分变为score | challenge[i],即位运算中的按位或(OR); - 扩散开启:挑战某个关卡后,该关卡两侧相邻的关卡将会开启(若之前未开启)。
需要计算:初始最少需要多少积分,才能挑战完环形闯关游戏的所有N个关卡。
数据范围(来自原文档提示):
1 <= challenge.length <= 5 * 10^41 <= challenge[i] <= 10^14值域高达
10^14,说明位运算相关操作必须按2^47量级以内的整数处理,long(Java/C++)或 Python 原生整数即可容纳;N达到5 * 10^4,意味着算法复杂度必须控制在O(N)级别左右。
规则背后的核心结构:连续区间闭包
关键性质:已挑战关卡始终构成一个连续区间
规则 3 规定「挑战某个关卡后,该关卡两侧相邻的关卡开启」。结合规则 1(初始只开启一个关卡),可以归纳出两个决定性问题形态的性质:
性质 1(区间性):任意时刻,已开启(已挑战)的关卡在环上必然构成一个连续的弧段(区间)。初始是单点;每次挑战该弧段左端或右端相邻的一个关卡,弧段两端各向外扩展一格。所谓「开启」,只会发生在当前已挑战弧段的两个端点外侧。
性质 2(端点可达性):在任一时刻,小扣只能挑战当前弧段的左端外侧或右端外侧的那个关卡。因为环上只有这两个关卡处于「开启」状态(已挑战关卡本身不会再挑战,其余关卡尚未开启)。
由此,「挑战完所有关卡」等价于:从某个起点出发,通过一系列向左或向右扩展,把弧段长度从 1 增长到N。这是一个典型的双向区间扩张问题,而不是任意的「点集跳转」问题。
积分演进的位运算本质
设当前积分为score,已挑战弧段的关卡编号集合为S,则:
- 挑战前,
score由初始积分与已挑战关卡积分按位或累加而来; - 挑战一个新关卡
v后,score := score | challenge[v]。
由于按位或只会累加 1 位、不会清除任何已有的 1 位,积分在闯关过程中单调不减,且每个关卡最多贡献一次。这带来一个直接推论:挑战顺序中,某个关卡一旦能被挑战,其在任意后续时刻也必然仍能被挑战(积分只增不减,且已开启关卡不会关闭)。因此贪心策略有天然的可行性空间——关键在于如何确定「能挑战」的时机与初始积分。
三个示例的完整推演
原文档给出了三组示例,这里逐一展开位运算过程,便于对照理解闭包扩展的细节。
示例 1:challenge = [5,4,6,2,7],答案4
- 初始选择编号 3 的关卡开启,积分
4; - 挑战编号 3:
4 | 2 = 6,开启 2、4; - 挑战编号 2:
6 | 6 = 6,开启 1; - 挑战编号 1:
6 | 4 = 6,开启 0; - 挑战编号 0:
6 | 5 = 7; - 挑战编号 4,全部完成。
观察:从起点 3 出发,弧段依次扩为[3] → [2,3] → [1,2,3] → [0,1,2,3] → [0..4],每次扩展都恰好满足「新关卡积分 ≤ 当前积分」,且积分随 OR 累加。
示例 2:challenge = [12,7,11,3,9],答案8
- 初始选择编号 3 的关卡开启,积分
8; - 挑战编号 3:
8 | 3 = 11,开启 2、4; - 挑战编号 2:
11 | 11 = 11,开启 1; - 挑战编号 4:
11 | 9 = 11,开启 0; - 挑战编号 1:
11 | 7 = 15; - 挑战编号 0,全部完成。
注意这里的扩展顺序是「先右后左」的混合路径:[3] → [2,3] → [2,3,4] → [0,2,3,4] → [0..4]([0,2,3,4]在环上即弧段4 → 0 → 2 → 3的连续形式),说明贪心扩展时左右两端可以交错推进,只要每次都能满足积分门槛即可。
示例 3:challenge = [1,1,1],答案1
三个关卡积分都是 1,初始积分 1 即可:任意选一个起点,1 | 1 = 1,随后左右两端分别扩展,始终满足门槛,全部通关。
小结
- 初始积分的候选范围是
[1, max(challenge)],因为若初始积分超过max(challenge),任意关卡都可作为起点,答案不可能超过它; - 示例的答案都不是
max(challenge)本身(示例 1 为 4,max=7),说明合理选择起点 + 左右交替扩展能显著降低初始积分需求,这正是本题的难点与乐趣所在。
解法推导:为什么不能用二分答案
一个常见的直觉是:初始积分越大越容易通关,因此可以对s二分。但实验表明这个直觉是错误的。
考虑challenge = [2, 7],起点选编号 0(积分 2 的关卡):
- 初始积分
s = 5:挑战编号 0,5 | 2 = 7 ≥ 7,再挑战编号 1,成功——可行; - 初始积分
s = 6:挑战编号 0,6 | 2 = 6 < 7,编号 1 无法挑战——不可行!
5可行而6不可行,说明可行性关于初始积分 s 并不单调。原因是 OR 运算的「位模式」特性:6的二进制是110,与2(010)或后仍是110,并没有获得7(111)所需的最高位;而5(101)或上2恰好补全为111。初始积分更大,不代表它与后续关卡的积分按位或之后能覆盖更多关卡。
因此正确做法是:从小到大枚举初始积分s,对每个s判定是否存在一条从某个起点出发、能通关全部关卡的扩展路径,第一个可行的s即为答案。枚举下界为 1,上界为max(challenge),由于答案在[1, max]内,整体可通过「逐 s 判定」的方式求解(若把每次判定做到 O(N),总复杂度为 O(N · max),仅适合小范围理解;大规模数据需结合下面的贪心优化与剪枝)。
贪心闭包判定:固定 s 下的 O(N) 验证
对于固定的初始积分s,如何判断能否通关?结合「弧段连续 + 积分单调不减」两条性质,可以设计如下贪心闭包过程(对一个候选起点):
- 令已挑战弧段为
[l, r](初始l = r = start),当前积分score = s | challenge[start];若challenge[start] > s,该起点不可用; - 循环直到弧段覆盖全部
N个关卡:- 若左侧邻居
(l-1) mod N未挑战且challenge[l-1] <= score,向左扩展:l ← l-1,score |= challenge[l]; - 否则若右侧邻居
(r+1) mod N未挑战且challenge[r+1] <= score,向右扩展:r ← r+1,score |= challenge[r]; - 若两端都无法扩展,则该起点在积分
s下不能通关;
- 若左侧邻居
- 弧段覆盖全部关卡即通关成功。
为什么贪心是安全的:任何时刻可挑战的关卡只有弧段两端,而积分单调不减意味着——如果某一端当前可挑战,那么先挑战它对另一端没有任何负面影响(积分只增不减);如果两端都不可挑战,则以后也永远不可挑战(因为积分只会更大,但两端门槛不变,若现在challenge > score,之后score的 OR 累加只会让数值更大……这里需要小心:OR 可能让数值变大从而满足门槛,这正是扩展会发生的场景;一旦某端满足门槛,立即扩展即可,不存在「等一等更优」的情况,因为延迟扩展不会降低另一端门槛,也不会带来额外的积分收益之外的好处)。因此,对任意固定起点,只要存在某种扩展顺序能通关,贪心的「能扩展就扩展」策略也一定能通关。
我在本地用 Python 实现了两套相互独立的方法并交叉验证:
- 精确校验器:对每个
s,从所有满足challenge[i] <= s的起点出发做 BFS,枚举弧段状态(位掩码表示)的所有合法扩展,判断是否能到达全满掩码; - 贪心闭包:上述左右扩展贪心。
在N ∈ [2, 8]、challenge[i] ∈ [1, 63]的 4000 组随机用例、覆盖全部s ∈ [1, max]的约20.6 万个子实例上,两者的判定结果完全一致(0 失配),验证了贪心闭包判定与精确枚举的等价性。
参考实现(Python,小数据教学版)
def can_finish(challenge, s, start): """固定初始积分 s、固定起点 start,贪心闭包判定是否可通关""" n = len(challenge) if challenge[start] > s: return False cleared = [False] * n cleared[start] = True cnt = 1 score = s | challenge[start] l = r = start while cnt < n: left = (l - 1) % n right = (r + 1) % n if not cleared[left] and challenge[left] <= score: l = left cleared[l] = True score |= challenge[l] cnt += 1 elif not cleared[right] and challenge[right] <= score: r = right cleared[r] = True score |= challenge[r] cnt += 1 else: return False return True def min_initial_score(challenge): """从小到大枚举初始积分 s,返回最小可行值""" for s in range(1, max(challenge) + 1): for start in range(len(challenge)): if can_finish(challenge, s, start): return s return -1 # 题目保证有解复杂度说明
can_finish单次判定为O(N);- 朴素枚举
s与起点为O(max(challenge) · N²),仅适合理解算法与小规模验证; - 面向
N = 5 * 10^4、challenge[i] ≤ 10^14的正式实现需要进一步优化(例如对每个起点先求「左闭包」与「右闭包」的可达区间,再合并判断,或结合数据结构的剪枝),但贪心闭包这一判定核心保持不变。优化方向不属于原文档内容,这里不作展开,读者可在理解本文原理后自行设计。
自建验证用例
除原文档三组示例外,可将下列用例用于自测(前两个来自对算法边界的针对性构造):
challenge | 最小初始积分 | 说明 |
|---|---|---|
[2, 7] | 5 | 若s = 5,起点 0 可行(5\|2=7);若s = 6,6\|2=6 < 7不可行,展示非单调性 |
[29, 11] | 20 | s=24时24\|11=27 < 29不可行,而s=20时20\|11=31 ≥ 29可行,进一步验证非单调 |
[5, 4, 6, 2, 7] | 4 | 原文档示例 1 |
[12, 7, 11, 3, 9] | 8 | 原文档示例 2 |
[1, 1, 1] | 1 | 原文档示例 3 |
小结
LCP 49. 环形闯关游戏的核心解题链条是:
- 建模:把「开启」扩散规则翻译成「已挑战关卡构成连续弧段,每次只能扩展弧段两端」的区间扩张模型;
- 位运算:积分演进为
score := score | challenge[i],单调不减,是贪心的基础; - 判定:对固定初始积分
s,贪心闭包(能扩就扩)与精确枚举等价,可 O(N) 判定; - 枚举:由于可行性对
s不单调(存在[2, 7]、[29, 11]反例),不能二分,须从小到大枚举s,取首个可行值。
该题在原仓库 lcp/README.md 中被标记为位运算、并查集、数组、堆(优先队列)标签,说明官方归类还提示了并查集合并连通块等进阶思路——本文的区间闭包视角正是理解那些优化方案的直观基础。若你对后续优化实现感兴趣,可以在仓库的 lcp 目录持续关注该题的题解更新。
- 示例工程
【免费下载链接】leetcode
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
相关推荐
LeetCode LCP 12 小张刷题计划:贪心 + 二分查找求解最小最大单日耗时
LeetCode LCP 12 小张刷题计划:贪心 + 二分查找求解最小最大单日耗时 导读 本文围绕 LeetCode 题库 LCP 系列经典题目「小张刷题计划
示例工程MAS 激活脚本怎么跑:按目标选 4 条 Windows 激活路径的实操手册
MAS 激活脚本怎么跑:按目标选 4 条 Windows 激活路径的实操手册 MAS(Microsoft Activation Scripts)是一个开源、免费
运维doocs/leetcode 题解精讲:LCP 40「心算挑战」——贪心 + 排序求最大偶数和
doocs/leetcode 题解精讲:LCP 40「心算挑战」——贪心 + 排序求最大偶数和 导读 「心算挑战」是力扣 LCP(LeetCode CUP)系列
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考