题目:
地上有一个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。请问该机器人能够到达多少个格子?
leetcode原题链接
思路:
DFS+剪枝,类似《矩阵中的路径》
思路参考
定义两个子函数
- nowsum(m,n),计算当前坐标(m,n)的数位之和
- dfs(m,n,visit,k,i,j),对当前坐标(m,n)的上下左右进行遍历判断
代码:
class Solution:
def movingCount(self, m: int, n: int, k: int) -> int:
def nowsum(m,n):#当前坐标(m,n)的数位之和
p,q=len(str(m)),len(str(n))
s=0
for i in range(p-1,-1,-1):
s+=m//(10**i)
m=m%(10**i)
#此时的s是m的数位和
for i in range(q-1,-1,-1):
s+=n//(10**i)
n=n%(10**i)
return s#此时s是m和n的数位之和
def dfs(m,n,visit,k,i,j):
if not 0 <= i < m or not 0 <= j < n: return 0#索引越界
elif nowsum(i,j)>k: return 0#数位和大于k,不符合要求
elif (i,j) in visit: return 0#该格已被访问过,不符合要求
visit.append((i,j))
return 1+dfs(m,n,visit,k,i + 1, j) + dfs(m,n,visit,k,i - 1, j) + dfs(m,n,visit,k,i, j + 1) + dfs(m,n,visit,k,i, j - 1)
visit=list()
return dfs(m,n,visit,k,0,0)
优化:
由于机器人的起始位置是坐标为[0, 0]的点,即左上角,所以仅考虑向下和向右的移动方向即可 ,优化后执行时间减半,内存消耗明显减少
return 1+dfs(m,n,visit,k,i + 1, j) + dfs(m,n,visit,k,i, j + 1)
错误代码:
- 思路:两个for循环遍历方格中所有区域,进行当前位置数位和与k的判断,若符合要求,count+1
- 错误原因:丢弃了一个重要特征,题目背景是机器人的移动,每次可以向左、右、上、下移动一格,不能横跨多个格子移动。
例如- 输入m=16,n=8,k=4,符合题目要求的格子有15个
i=0,j=0,1,2,3,4
i=1,j=0,1,2,3
i=2,j=0,1,2
i=3,j=0,1
i=4,j=0 - 而代码运行结果还会有i=10,j=0,1,2,3,4…等
- 输入m=16,n=8,k=4,符合题目要求的格子有15个
- 错误在于,当出现第一个不符合要求的格子时【i=0,j=5】执行continue【结束本次循环,直接进行下一次循环的判断条件】,只结束了本次循环,仍会对下一个元素【i=0,j=5】进行判断,继续遍历直至【i=10,j=0】,判断发现数位和小于k,count执行+1,实际上,机器人无法移动至该方格
class Solution:
def movingCount(self, m: int, n: int, k: int) -> int:
def nowsum(m,n):#当前坐标(m,n)的数位之和
p,q=len(str(m)),len(str(n))
p_sum=0
for i in range(p-1,-1,-1):
p_sum+=m//(10**i)
m=m%(10**i)
#此时的p_sum是m的数位和
for i in range(q-1,-1,-1):
p_sum+=n//(10**i)
n=n%(10**i)
return p_sum#此时p_sum是m和n的数位之和
count=0
for i in range(m):
for j in range(n):
if nowsum(i,j)<=k:
count+=1
else:
continue
return count
- 企图用break语句【强制退出当前for】来修改。
- 修改后效果:成功通过示例1,示例2失败
- 示例1:仍然输入m=16,n=8,k=4
- 当i=0时,j=0,1,2,3,4符合要求
直至j=5,数位和大于k(4),进入else语句,j重新赋值为0,break,强制终止内层循环,不再对j=6,7…进行遍历+判断。跳出内层for循环后,判断下一个i值,在j=0的条件下是否符合【数位和不大于k】的要求,若不符合,则再强制跳出外层for循环,直接return此时count值。 - i=4时,j=0,符合要求,j=1,数位和大于k,进入else语句,j重新赋值0,break跳出内层for循环(i=4时的情况),对下一个i进行遍历,i=5时最小数位和都大于k,判定比5更大的i也不可能符合要求。直接break跳出外层循环,return。
- 当i=0时,j=0,1,2,3,4符合要求
- 示例2:m=38,n=15,k=9
- i=0,j=0,1,2…14;
- 【错误】i=1,j=0,1,2,3,4,5,6,7,8符合要求,j=9时数位和大于k(9),判定i=1时,符合要求的格子已搜寻完毕,break跳出内层for循环(i=1的情况)
- i=1,j=10,这个格子符合要求,可以从【i=0,j=10】此格往下移动一格
- 示例1:仍然输入m=16,n=8,k=4
- 放弃debug,采用递归来做【类似《矩阵中的路径》】
count=0
for i in range(m):
for j in range(n):
if nowsum(i,j)<=k:
count+=1
else:
j=0
break
if nowsum(i,j)>k:
break
return count
额外学到的知识:
- continue和break用于改变for循环的执行流程。二者区别如下:
continue:用来结束当前当次循环,跳出循环体中下面尚未执行的语句,但不跳出当前循环。
for i in range(4):
if i>1:
print("执行了if语句")
continue
print("执行了后续语句")
i=0时,if判断,False,不进入if内的语句,执行
print("执行了后续语句")
i=1,if判断,False,不进入if内的语句,执行print("执行了后续语句")
i=2,if判断,True,进入if内语句,执行print("执行了if语句")
,执行continue
,结束本次i=2的循环,不再执行后续语句,进行下一次for循环遍历(i=3)
i=3,if判断,True,进入if内语句,执行print("执行了if语句")
,执行continue
,结束本次i=2的循环,不再执行后续语句,后续无 需执行的元素。
break:跳出(最内层)循环,不再执行
for i in range(4):
if i>1:
print("执行了if语句")
break
print("执行了后续语句")
i=0时,if判断,False,不进入if内的语句,执行
print("执行了后续语句")
i=1,if判断,False,不进入if内的语句,执行print("执行了后续语句")
i=2,if判断,True,进入if内语句,执行print("执行了if语句")
,执行break
,结束for循环。