(python版)《剑指Offer》JZ66:机器人的运动范围

牛客
Leetcode

【题目描述】

地上有一个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。请问该机器人能够到达多少个格子?
在这里插入图片描述

【解题思路】

本题与 矩阵中的路径 类似,是典型的搜索 & 回溯问题。在介绍回溯算法算法前,为提升计算效率,首先讲述两项前置工作: 数位之和计算可达解分析

1.数位之和计算

设一数字 x ,向下取整除法符号 // ,求余符号 ⊙ ,则有:

  • x⊙10 :得到 x 的个位数字
  • x // 10 : 令 x 的十进制数向右移动一位,即删除个位数字

因此,可通过循环求得数位和 s ,数位和计算的封装函数如下所示(将每个个位数累加)

def sums(x):
    s = 0
    while x != 0:
        s += x % 10
        x = x // 10
    return s

由于机器人每次只能移动一格(即只能从 x 运动至 x±1),因此每次只需计算 x 到 x±1 的数位和增量。本题说明 1≤n,m≤100 ,以下公式仅在此范围适用。
在这里插入图片描述
n,m 最大是99,99,数位和最大是36,故满足上述增量格式

当(x+1)⊙10 ≠ 0当(x+1)⊙10 = 0
0-9 以内数位和相差19 10 的数位和分别为9 1,相差8
11-19 以内数位和相差119 20 的数位和分别为10 2,相差8
21-29 以内数位和相差129 30 的数位和分别为11 3,相差8
31-39 以内数位和相差139 40 的数位和分别为12 4,相差8
41-49 以内数位和相差149 50 的数位和分别为13 5,相差8

故:以下代码为增量公式的三元表达式写法,

s_x+1 if (x+1)%10 else s_x-8

2.可达解分析

根据数位和增量公式得知,数位和 每逢 进位 突变一次。根据此特点,矩阵中 满足数位和的解 构成的几何形状形如多个 等腰直角三角形 ,每个三角形的直角顶点位于 0, 10, 20, … 等数位和突变的矩阵索引处 。
.
三角形内的解虽然都满足数位和要求,但由于机器人每步只能走一个单元格,而三角形间不一定是连通的,因此机器人不一定能到达,称之为 不可达解 ;同理,可到达的解称为 可达解 (本题求此解) 。

图例展示了 n = m = 20, k∈[6,19] 的可达解不可达解非解,以及连通性的变化。
在这里插入图片描述

【法一】深度优先遍历 DFS

在这里插入图片描述
在这里插入图片描述

class Solution:
    def movingCount(self, m: int, n: int, k: int) -> int:
        def dfs(i, j, si, sj):
        	# 终止条件
            if i >= m or j >= n or k < si + sj or (i, j) in visited: 
            	return 0
            
            # 递推工作
            # 1.标记当前单元格
            visited.add((i,j))
            # 2.搜索下一单元格
            # xia_da = dfs(i + 1, j, si + 1 if (i + 1) % 10 else si - 8, sj)
        	#you_da = dfs(i, j + 1, si, sj + 1 if (j + 1) % 10 else sj - 8)
        	# total = 1 + xia_da + you_da
        	# print('下达:',xia_da,'\t','右达:',you_da,end='\t')
        	# print('total =',total)
			return 1 + dfs(i + 1, j, si + 1 if (i + 1) % 10 else si - 8, sj) + dfs(i, j + 1, si, sj + 1 if (j + 1) % 10 else sj - 8)
        visited = set()
        return dfs(0, 0, 0, 0)
'''
作者:jyd
链接:https://leetcode-cn.com/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/solution/mian-shi-ti-13-ji-qi-ren-de-yun-dong-fan-wei-dfs-b/
'''
【打印版 m=3, n=4, k=2】
visited =  {(0, 0)}
visited =  {(1, 0), (0, 0)}
visited =  {(1, 0), (2, 0), (0, 0)}
下达: 0         右达: 0       total = 1
visited =  {(1, 0), (1, 1), (2, 0), (0, 0)}
下达: 0         右达: 0       total = 1
下达: 1         右达: 1       total = 3
visited =  {(0, 1), (0, 0), (1, 1), (2, 0), (1, 0)}
visited =  {(0, 1), (0, 0), (1, 1), (2, 0), (0, 2), (1, 0)}
下达: 0         右达: 0       total = 1
下达: 0         右达: 1       total = 2
下达: 3         右达: 2       total = 6

复杂度分析:

设矩阵行列数分别为 M, N 。

  • 时间复杂度 O(MN) : 最差情况下,机器人遍历矩阵所有单元格,此时时间复杂度为 O(MN) 。
  • 空间复杂度 O(MN): 最差情况下,Set visited 内存储矩阵所有单元格的索引,使用 O(MN) 的额外空间。

【法二】广度优先遍历 BFS

在这里插入图片描述
在这里插入图片描述

class Solution:
    def movingCount(self, m: int, n: int, k: int) -> int:
	     # 初始化
	    queue, visited = [(0, 0, 0, 0)], set()
	    while queue:    # 终止条件: queue 为空
	        # 1.单元格出队
	        i, j, si, sj = queue.pop(0)
	        # 2.判断是否跳过
	        if i >= m or j >= n or k < si + sj or (i, j) in visited: 
	            continue
	        
	        # 3.标记当前单元格
	        visited.add((i,j))
	        # print('visited = ',visited)
	        # 4.单元格入队
	        queue.append((i + 1, j, si + 1 if (i + 1) % 10 else si - 8, sj))
	        queue.append((i, j + 1, si, sj + 1 if (j + 1) % 10 else sj - 8))
	        # print('出队后,当前queue:',queue)
        return len(visited)
'''
作者:jyd
链接:https://leetcode-cn.com/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/solution/mian-shi-ti-13-ji-qi-ren-de-yun-dong-fan-wei-dfs-b/
'''
visited =  {(0, 0)}
出队后,当前queue: [(1, 0, 1, 0), (0, 1, 0, 1)]
visited =  {(1, 0), (0, 0)}
出队后,当前queue: [(0, 1, 0, 1), (2, 0, 2, 0), (1, 1, 1, 1)]
visited =  {(1, 0), (0, 1), (0, 0)}
出队后,当前queue: [(2, 0, 2, 0), (1, 1, 1, 1), (1, 1, 1, 1), (0, 2, 0, 2)]
visited =  {(1, 0), (0, 1), (2, 0), (0, 0)}
出队后,当前queue: [(1, 1, 1, 1), (1, 1, 1, 1), (0, 2, 0, 2), (3, 0, 3, 0), (2, 1, 2, 1)]
visited =  {(0, 1), (0, 0), (1, 1), (2, 0), (1, 0)}
出队后,当前queue: [(1, 1, 1, 1), (0, 2, 0, 2), (3, 0, 3, 0), (2, 1, 2, 1), (2, 1, 2, 1), (1, 2, 1, 2)]
visited =  {(0, 1), (0, 0), (1, 1), (2, 0), (0, 2), (1, 0)}
出队后,当前queue: [(3, 0, 3, 0), (2, 1, 2, 1), (2, 1, 2, 1), (1, 2, 1, 2), (1, 2, 1, 2), (0, 3, 0, 3)]
6

复杂度分析:

设矩阵行列数分别为 M, N。

  • 时间复杂度 O(MN) : 最差情况下,机器人遍历矩阵所有单元格,此时时间复杂度为 O(MN) 。
  • 空间复杂度 O(MN): 最差情况下,Set visited 内存储矩阵所有单元格的索引,使用 O(MN) 的额外空间。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值