426.拨号器
问题描述
小F正在使用一个骑士跳跃方式的拨号器。这个拨号器是一个类似电话键盘的 3x4 矩阵,每个数字对应一个单元格,骑士只能站在蓝色数字单元格上进行跳跃(数字 1 到 9 和 0)。骑士的移动方式和国际象棋中的马相同:它可以垂直移动两个单元格并水平移动一个单元格,或水平移动两个单元格并垂直移动一个单元格,形成 "L" 形。
123
456
789
*0#
给定一个整数 n,你需要帮助小F计算骑士可以拨出的所有长度为 n 的不同电话号码的数量。骑士可以从任何数字开始,并在 n-1 次有效跳跃后得到一个有效号码。答案可能非常大,因此你需要返回对 10^9 + 7 取模的结果。
测试样例
样例1:
输入:
n = 1
输出:10
样例2:
输入:
n = 2
输出:20
样例3:
输入:
n = 3
输出:46
样例4:
输入:
n = 4
输出:104
思路
用动态规划的思路解决。定义 dp[i][j] 表示长度为 i 的号码,且最后一位是数字 j 的数量。i 代表跳跃次数,j 代表数字,dp[i][j] 表示以 j 为结尾的有效电话号码的数量。
假设 dp[i][j] 是长度为 i 且以 j 结尾的电话号码数量,那么我们可以从数字 j 之前的数字(即跳跃到 j 的数字)转移过来。所以,dp[i][j] 可以通过所有 j 可以从其跳跃过来的数字的 dp[i-1][k] 转移过来。
当 n = 1 时,每个数字都可以单独构成一个有效号码。所以 dp[1][j] = 1,对于每个 j 从 0 到 9。
我们最终需要的是 dp[n][0] + dp[n][1] + ... + dp[n][9],即所有长度为 n 的有效电话号码的数量。
代码实现
MOD = 10**9 + 7
# 定义跳跃图
jumps = {
0: [4, 6],
1: [8, 6],
2: [7, 9],
3: [4, 8],
4: [3, 9, 0],
5: [],
6: [1, 7, 0],
7: [2, 6],
8: [1, 3],
9: [2, 4]
}
def solution(n: int) -> int:
# dp[i][j] 表示长度为 i 且以 j 结尾的有效电话号码的数量
dp = [[0] * 10 for _ in range(n + 1)]
# 初始化:长度为 1 的号码,所有数字都可以作为单独的号码
for i in range(10):
dp[1][i] = 1
# 动态规划计算所有长度为 n 的号码
for i in range(2, n + 1):
for j in range(10):
# dp[i][j] 由从 j 能跳跃到的数字的 dp[i-1][k] 转移而来
for k in jumps[j]:
dp[i][j] = (dp[i][j] + dp[i-1][k]) % MOD
# 结果是所有长度为 n 的号码的和
result = sum(dp[n]) % MOD
return result
时间复杂度:
动态规划的时间复杂度为 O(n * 10 * 4),其中 n 是电话号码的长度,10 是数字的个数,4 是每个数字最多的跳跃数。因此,总时间复杂度为 O(n),对于常见的 n 值来说是高效的。