题目难度: 中等
原题链接
今天继续更新剑指 offer 系列, 这道题有至少三种解法, 很适合扩展思路
老样子晚上 6 点 45 分准时跟大家见面, 大家在我的公众号"每日精选算法题"中的聊天框中回复 offer 就能看到剑指 offer 系列当前连载的所有文章了
大家有什么想法建议和反馈的话欢迎随时交流, 包括但不限于公众号聊天框/知乎私信评论等等~
地上有一个 m 行 n 列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于 k 的格子。例如,当 k 为 18 时,机器人能够进入方格 [35, 37] ,因为 3+5+3+7=18。但它不能进入方格 [35, 38],因为 3+5+3+8=19。请问该机器人能够到达多少个格子?
m = 2, n = 3, k = 1
3
m = 3, n = 1, k = 0
1
O(MN)
O(MN)
O(MN)
class Solution:
def movingCount(self, m: int, n: int, k: int) -> int:
# 方法1: 经典BFS, 队列+visit集合
q = [(0, 0)]
v = set(q)
def getSum(x):
# 求数位和, 即循环累加模10的结果然后除以10即可
res = 0
while x:
res += x % 10
x //= 10
return res
# 注意: 这里也可以将数字转换成字符串, 然后逐位字符转int求sum
# return sum([int(c) for c in str(x)])
def isValid(r, c):
# 判断某个下标是否有效: 是否在方格内/行列数位和是否不大于k/是否被访问过
return 0 <= r < m and 0 <= c < n and getSum(r) + getSum(c) <= k and (r, c) not in v
for point in q:
r, c = point
# 遍历四个方向的邻居
for rr, cc in ((r + 1, c), (r - 1, c), (r, c + 1), (r, c - 1)):
if isValid(rr, cc):
# 如果邻居有效的话, 将其加入BFS的queue里继续遍历
q.append((rr, cc))
v.add((rr, cc))
return len(v)
剑指 Offer 12. 矩阵中的路径 - leetcode 剑指offer系列
(大家在公众号里回复 offer 就能看到了)的 DFS 不一样的是, 这道题的 DFS 没有在递归调用邻居之后将其从 visit 中移除. 这是因为上道题中一个点可能出现在多条路径中, 所以必须调用完之后从集合移除, 避免之后的路径无法使用该节点; 而这道题只有一个共享的运动范围, 所以一个点如果出现在运动范围内的话, 那么它一定不需要再次被访问了, 直接永久加入 visit 集合中即可O(MN)
O(MN)
O(MN)
class Solution:
def movingCount(self, m: int, n: int, k: int) -> int:
# 方法2: 经典DFS, 递归+visit集合
v = {(0, 0)}
def getSum(x):
# 求数位和, 即循环累加模10的结果然后除以10即可
res = 0
while x:
res += x % 10
x //= 10
return res
# 注意: 这里也可以将数字转换成字符串, 然后逐位字符转int求sum
# return sum([int(c) for c in str(x)])
def isValid(r, c):
# 判断某个下标是否有效: 是否在方格内/行列数位和是否不大于k/是否被访问过
return 0 <= r < m and 0 <= c < n and getSum(r) + getSum(c) <= k and (r, c) not in v
def dfs(r, c):
for rr, cc in ((r + 1, c), (r - 1, c), (r, c + 1), (r, c - 1)):
if isValid(rr, cc):
# 如果邻居有效的话, 将其加入集合中, 递归调用dfs该邻居
v.add((rr, cc))
dfs(rr, cc)
# 初始从起点开始dfs
dfs(0, 0)
return len(v)
dp[r,c]
表示下标(r,c)
的点的可达性, 能走到就是 True, 否则就是 Falsedp[0,0] = True
, 起点一定可达(r,c)
的数位和大于 k, 直接将其 dp 值设为 Falsedp[r,c] = dp[r-1,c] or dp[r,c-1]
O(MN)
O(MN)
class Solution:
def movingCount(self, m: int, n: int, k: int) -> int:
# 方法3: DP
# 如果rc数位和大于k, dp[r,c] = False
# 否则dp[r,c] = dp[r-1,c] or dp[r,c-1]
# 这里使用集合代替该bool字典, 初始元素是起点下标
dp = {(0, 0)}
# 另一个优化: 缓存数位和结果, 避免重复计算
memo = {}
def getSum(x):
# 求数位和, 即循环累加模10的结果然后除以10即可
if x not in memo:
res = 0
cur = x
while cur:
res += cur % 10
cur //= 10
memo[x] = res
# 注意: 这里也可以将数字转换成字符串, 然后逐位字符转int求sum
# memo[x] = sum([int(c) for c in str(x)])
return memo[x]
for r in range(m):
for c in range(n):
if getSum(r) + getSum(c) <= k and ((r-1, c) in dp or (r, c-1) in dp):
dp.add((r, c))
return len(dp)
大家可以在下面这些地方找到我~
我的知乎专栏
我的 CSDN
我的 Leetcode
我的牛客网博客
我的公众号: 每日精选算法题, 欢迎大家扫码关注~