LeetCode 488. 祖玛游戏
字数 1676 2025-11-04 08:32:42
LeetCode 488. 祖玛游戏
题目描述
你有一个祖玛游戏:一条轨道上有一串彩色球(用字符串 board 表示),你手里有另外一些球(用字符串 hand 表示)。游戏规则是:
- 每次你可以从
hand中选一个球,插入到board的任意位置。 - 如果插入后,存在三个或更多相同颜色的球连在一起,它们就会消失,并且左右两边的球会靠拢;如果靠拢后再次形成三个或以上连续同色球,会继续消除(连锁反应)。
- 目标是用最少的步数(每次插入一个球算一步)让
board清空。如果无法清空,返回-1。
示例
board = "WRRBBW", hand = "RB"
输出: -1
解释:无法清空轨道上的球。
board = "WWRRBBWW", hand = "WRBRW"
输出: 2
解释:一种方案:WWRRBBWW → WWRRRBBWW → WWBBWW → WWWW → 空
解题思路
这是一个状态搜索问题,适合用 BFS 或 DFS+剪枝 求解。因为要求最少步数,BFS 更直观(首次到达空棋盘即最优解)。
难点在于:
- 插入球的位置很多(
board.length()+1个位置)。 - 插入后可能触发连锁消除,需要模拟消除过程。
- 需要避免重复状态(相同棋盘 + 相同手持球集合视为同一状态)。
步骤详解
第 1 步:状态表示
将当前棋盘状态和手持球的状态作为一个整体状态。
- 棋盘用字符串
board表示。 - 手持球用字符串
hand表示(注意:手持球的顺序不重要,可以用排序或计数来去重)。
为了去重,我们可以用(board, hand)作为状态,但hand最好按字符排序存储,避免因顺序不同重复访问。
第 2 步:模拟消除过程
写一个函数 eliminate(board):
- 用类似栈的方法扫描字符串,检测连续相同字符长度 ≥ 3 的区间并删除,重复直到不能消除。
具体做法:
- 初始化栈(或直接遍历+双指针模拟)。
- 遍历
board,逐个字符检查:- 如果当前字符与栈顶字符相同,继续压栈。
- 否则,检查栈中刚积累的相同字符长度是否 ≥ 3,如果是,弹出这些字符,然后继续处理。
- 注意:消除后当前字符可能与新栈顶相同,所以要小心处理。
- 更简单的方法:循环扫描,每次找出所有连续 ≥ 3 的区间,一次性删除,然后重新拼接字符串,重复直到没有可消除的区间。
示例:
"WWRRBBWW" 插入 'R' 后成 "WWRRRBBWW"
- 扫描到
"RRR"消除 →"WWBBWW" - 扫描到
"BB"不够 3 个,"WW"也不够 3 个,停止。
第 3 步:BFS 框架
- 初始状态:
(board, hand)。 - 队列中存储
(board, hand, step),但step可以从 BFS 层数获得。 - 对于每个状态,枚举手持球中的每个球(去重:如果连续相同球,只试第一个避免重复),再枚举插入到棋盘的所有位置(去重:如果插入位置前后字符相同,只插在连续相同块的第一个位置后面?这里可以剪枝,但简单起见先枚举所有位置)。
- 插入后调用
eliminate得到新棋盘。 - 如果新棋盘为空,返回
step+1。 - 如果新棋盘非空,且手持球还有剩余,将新状态
(newBoard, newHand)加入队列(去重检查)。 - 用
visited集合记录已访问的(board, hand)状态。
第 4 步:剪枝优化
- 手持球中某个颜色在棋盘上不存在,且不是用于消除的,则不能直接帮助消除,但可能连锁消除中间部分,所以不能直接剪掉。
- 更有效的剪枝:如果手持球颜色与棋盘上相邻颜色不同,且无法与相邻球形成 ≥ 3 的连续,则插入无效?但连锁消除可能后续发生,所以不能简单判断。
- 主要靠 BFS 层数保证最少步数,状态去重避免重复扩展。
第 5 步:代码结构(伪代码)
function findMinStep(board, hand):
对手持球排序(用于去重)
visited = set()
queue = deque([(board, hand)])
steps = 0
while queue:
for 当前层大小:
cur_board, cur_hand = queue.popleft()
for 每个手持球颜色(去重):
for 每个插入位置 in [0, len(cur_board)]:
new_board = insert(cur_board, pos, color)
new_board = eliminate(new_board) # 连锁消除
if new_board 为空: return steps+1
new_hand = remove color from cur_hand
if (new_board, new_hand) 未访问:
visited.add(...)
queue.append((new_board, new_hand))
steps++
return -1
关键点总结
- BFS 保证最短路径。
- 状态去重:
(board, hand)的规范化表示(hand 排序)。 - 消除模拟:要正确实现连锁消除,可用循环扫描直到不变。
- 效率:棋盘长度 ≤ 20,手持球长度 ≤ 5,状态数可能很大,但去重后可控。
通过以上步骤,你可以逐步实现这个经典的搜索题目,理解 BFS 在游戏类问题中的应用。