You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何高效求解N×N棋盘黑色方块的最少移动步数及路径?

Great question! 回溯法虽然能遍历所有可能找到最优解,但一旦棋盘尺寸N稍微大一点(比如N>5),状态爆炸问题会让它慢到完全不可用。我之前研究过类似的网格合并/移动问题,这里分享几个高效的思路,专门针对你要的最少步数移动序列求解:

核心观察:先搞懂问题本质

在动手写高效算法前,先拆解下规则的关键性质:

  • 每个合法移动必然让黑块总数减少1:移动的黑块从原位置变白,目标位置本来就是黑块,所以总数从b变为b-1。
  • 游戏终止的条件:没有任何黑块的左上(row-1, column-1)或右上(row-1, column+1)位置是黑块——换句话说,所有黑块的斜上方左右都没有其他黑块。
  • 最少步数 = 初始黑块数 - 终止状态的最小可能黑块数。所以问题本质是找到能把黑块合并到最少数量的合法移动序列。
高效解法推荐

1. 动态规划+路径回溯(最优且高效)

这是我最推荐的方法,时间复杂度仅为O(N²),适合任意尺寸的棋盘,还能直接生成移动序列。

思路

我们用一个dp二维数组,dp[x][y]表示以(x,y)为最终保留的黑块,最多能合并多少个黑块(包括它自己)。从棋盘最底层往上遍历计算dp值,再通过回溯dp数组生成移动序列。

步骤

第一步:计算DP数组

从底到顶遍历每个位置:

n = len(board)
dp = [[0]*n for _ in range(n)]

# 从最底层行开始往上遍历
for x in range(n-1, -1, -1):
    for y in range(n):
        if board[x][y] == 1:
            # 检查左下方是否有可合并的黑块
            left_down = dp[x+1][y-1] if (x+1 < n and y-1 >= 0) else 0
            # 检查右下方是否有可合并的黑块
            right_down = dp[x+1][y+1] if (x+1 < n and y+1 < n) else 0
            # 当前位置能合并的总数 = 自己 + 下方最多可合并的数量
            dp[x][y] = 1 + max(left_down, right_down)

第二步:回溯生成移动序列

通过递归回溯dp数组,从最底层的可合并黑块开始记录操作,保证移动顺序正确:

def backtrack(x, y, board, dp, moves):
    # 如果当前位置只能保留自己,没有可合并的下方黑块,直接返回
    if dp[x][y] <= 1:
        return
    
    left_down_val = dp[x+1][y-1] if (x+1 < len(board) and y-1 >= 0) else 0
    right_down_val = dp[x+1][y+1] if (x+1 < len(board) and y+1 < len(board)) else 0
    
    if left_down_val > right_down_val:
        # 先回溯左下方的黑块(处理更底层的移动)
        backtrack(x+1, y-1, board, dp, moves)
        # 记录当前移动操作
        moves.append(f"将[{x+1},{y-1}]处的黑色方块移动至[{x},{y}]")
        # 标记原位置为白色,避免重复处理
        board[x+1][y-1] = 0
    else:
        # 优先处理右下方(若值相等,任选其一即可,不影响最少步数)
        backtrack(x+1, y+1, board, dp, moves)
        moves.append(f"将[{x+1},{y+1}]处的黑色方块移动至[{x},{y}]")
        board[x+1][y+1] = 0

# 初始化移动序列列表
moves = []
# 遍历所有顶层黑块,回溯生成序列
for x in range(n):
    for y in range(n):
        if board[x][y] == 1:
            backtrack(x, y, board, dp, moves)

验证示例

用你给出的3×3棋盘测试:

  • 计算出dp[0][1] = 3(表示这个位置能合并3个黑块),dp[1][0]和dp[1][2]都是1。
  • 回溯时会先处理[1,0]的移动,再处理[1,2]的移动,生成的序列和你给出的示例完全一致。

2. 贪心策略(简单易实现)

如果不需要严格证明最优性,贪心策略是最容易上手的,同样是O(N²)时间复杂度。

思路

从棋盘最底层开始往上遍历,遇到黑块就优先检查是否能向上移动(先右上后左上,或反之),一旦能移动就立即执行并记录操作。因为底层的黑块只能向上移动,先处理它们不会影响上层黑块的合并,还能尽早减少黑块数量,避免不必要的状态分支。

步骤

n = len(board)
moves = []

# 从最底层行往上遍历
for x in range(n-1, 0, -1):
    for y in range(n):
        if board[x][y] == 1:
            # 先尝试右上移动
            if y+1 < n and board[x-1][y+1] == 1:
                moves.append(f"将[{x},{y}]处的黑色方块移动至[{x-1},{y+1}]")
                board[x][y] = 0
            # 再尝试左上移动
            elif y-1 >= 0 and board[x-1][y-1] == 1:
                moves.append(f"将[{x},{y}]处的黑色方块移动至[{x-1},{y-1}]")
                board[x][y] = 0

注意

这个策略在大多数情况下能得到最少步数,但如果存在多个合并路径选择时,可能需要调整遍历顺序(比如从右到左遍历列),不过对于你的问题规则,它的结果通常是最优的。

3. 基于对角线的状态压缩(适合超大棋盘)

如果棋盘尺寸特别大(比如N>20),可以利用对角线的性质压缩状态:

  • 观察到移动操作只会在两种对角线上进行:
    • 类型A:x - y = 常数(左上-右下方向)
    • 类型B:x + y = 常数(右上-左下方向)
  • 每条类型B对角线上的黑块最多可以合并成1个,需要k-1步(k为该对角线上的黑块数);同理类型A对角线也是如此。
  • 最终最小黑块数等于两种对角线中各自独立的“顶端”黑块数的交集,最少步数就是初始黑块数减去这个最小值。

这个方法适合理论分析或超大棋盘的快速计算,但生成具体移动序列需要额外的逻辑。


内容的提问来源于stack exchange,提问作者Firex Firexo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:02:01