骑士跳跃的数字游戏|豆包Marscode AI刷题

121 阅读5分钟

骑士跳跃的数字游戏:动态规划的应用与解题分析 在这篇文章中,我们将深入探讨如何使用动态规划来解决一个与棋盘上骑士跳跃(马走日)相关的问题。这个问题涉及在一个电话键盘上,骑士可以从一个数字跳到另一个数字,并且需要计算能够构成长度为 n 的所有有效电话号码的数量。由于可能的结果数量非常大,我们需要对结果进行模 10^9 + 7 操作。 问题分析 问题的核心是骑士跳跃的行为模式。这个模式基于国际象棋中的骑士(马)走法,即: 1.骑士可以垂直移动两个单元格,再水平移动一个单元格。 2.或者水平移动两个单元格,再垂直移动一个单元格。 这种跳跃方式形成了「L」形的移动规律。对于每个数字(1到9,还有0),骑士都可以选择从其周围的某些数字跳跃过来,并且每次的跳跃都会构成电话号码中的一部分。题目要求我们计算在这些跳跃限制下,长度为 n 的所有有效电话号码的数量。 解题思路:动态规划 解这道题的思路主要依赖动态规划(DP)。在这里,我们可以将问题分解为更小的子问题来进行解决。具体的步骤如下:

  1. 状态表示 我们设计一个二维数组 dp,其中 dp[i][j] 表示长度为 i + 1 的电话号码,以数字 j 结尾的有效数量。 3.例如,dp[0][j] 代表长度为 1 的电话号码,结尾是 j 的情况。 4.初始时,对于所有的 j,dp[0][j] = 1,因为每个数字本身都可以构成一个长度为 1 的电话号码。
  2. 状态转移 对于每一个长度为 i + 1 的电话号码,其结尾的数字 j 来自于一个「上一个数字」的集合。具体来说,对于每个数字 j,我们需要查看能跳跃到 j 的所有数字 prev,并将 dp[i-1][prev] 的结果累加到 dp[i][j] 中。 这是因为,如果一个长度为 i 的电话号码以 prev 结尾,那么长度为 i+1 的电话号码可以以 prev 为前一个数字,然后跳跃到 j。
  3. 终止条件 最终,我们需要求得所有长度为 n 的有效电话号码数量,也就是 dp[n-1][0] + dp[n-1][1] + ... + dp[n-1][9] 的和。由于数量可能非常大,我们对结果进行取模操作,模数为 10^9 + 7。
  4. 跳跃关系表 为了加速计算,我们首先定义一个「跳跃关系表」,表示每个数字可以跳跃到哪些数字。这个表可以由如下字典表示: moves = { 1: [6, 8], 2: [7, 9], 3: [4, 8], 4: [3, 9, 0], 5: [], 6: [1, 7, 0], 7: [2, 6], 8: [1, 3], 9: [2, 4], 0: [4, 6] } 这个表告诉我们,数字 1 可以跳到 6 和 8,数字 2 可以跳到 7 和 9,依此类推。 解题代码 MOD = 10**9 + 7

定义骑士可以移动到的下一个位置

moves = { 1: [6, 8], 2: [7, 9], 3: [4, 8], 4: [3, 9, 0], 5: [], 6: [1, 7, 0], 7: [2, 6], 8: [1, 3], 9: [2, 4], 0: [4, 6] } def solution(n: int) -> int: # 初始化 dp 数组 dp = [[0] * 10 for _ in range(n)] # 第一步每个数字都可以到达 for i in range(10): dp[0][i] = 1 # 动态规划更新 for step in range(1, n): for num in range(10): for next_num in moves[num]: dp[step][num] = (dp[step][num] + dp[step-1][next_num]) % MOD # 计算最终结果 result = sum(dp[n-1]) % MOD return result

测试用例

print(solution(1)) # 10 print(solution(2)) # 20 print(solution(3)) # 46 print(solution(4)) # 104 分析与思考 这个问题的核心挑战在于如何合理设计状态转移,并且将复杂度控制在可接受范围内。由于每次跳跃的数字是有限的(最多 3 个可能的跳跃),这使得我们的状态转移过程相对简单,能够在时间上进行有效的优化。 另外,通过设计一个 moves 字典,我们有效地将骑士的跳跃关系进行了存储,使得状态转移过程非常高效。这种设计思路既简洁又高效,尤其适用于这类结构化的问题。 时间与空间复杂度分析 5.时间复杂度:我们的主循环有 n 层,每一层都需要处理 10 个数字,并且对于每个数字,我们需要查询它能跳到的数字。每个数字最多有 3 个跳跃目标,因此总时间复杂度为 O(n * 10 * 3),即 O(n)。 6.空间复杂度:我们使用了一个二维数组 dp,其大小为 n * 10,因此空间复杂度为 O(n)。 结论: 这道题目通过动态规划方法成功解决了骑士跳跃问题,利用跳跃关系表和状态转移公式有效地计算出所有有效的电话号码数量。这种方法不仅高效,而且容易理解,是解决此类基于图遍历的问题的典型例子。