机器人位于如下图 m x n网格的左上角,通过移动到达网格的右下角。但它的每次移动只能是向下或者向右移动一格,请问从起点到终点共有多少种走法?

问题来自于leetcode第62号题目,经过深入地摸索发现,在这个机器人的魔性步伐里居然隐藏着一个“杨辉三角形”。先来看看,如何解决这个第62题:

方法一: 很明显,机器人到达地图上第一行或者第一列的任一点,都只有1种走法;第2行竟然就是一个自然数数列 1,2,3,4,.... ;到第3行发现第二格开始每一格都是前一格与上一格的和。于是用数学归纳法推断出一个结论:任意点的走法数是 map[i][j] = map[i-1][j] + map[i][j-1] ,i>0,j>0;map[0][j]=map[i][0]=1。

代码如下:

>>> def paths(m,n):map = [[1 for _ in range(n)] for _ in range(m)]for i in range(1,m):for j in range(1,n):map[i][j] = map[i-1][j] + map[i][j-1]return map[m-1][n-1]>>> m,n = 2,3
>>> paths(m,n)
3
>>> m,n = 3,3
>>> paths(m,n)
6
>>> m,n = 3,7
>>> paths(m,n)
28
>>> 

改进代码:

def paths2(m,n):if m<n: m,n=n,mmap = [[1]*m for _ in range(n)]map[1] = [i+1 for i in range(m)]for i in range(2,n):for j in range(1,m):map[i][j] = map[i][j-1]+map[i-1][j]return map[-1][-1]

方法二:还是上面的思路,把地图扩展到正方形,如下图所示每条对角线串连的数字正好组成一个标准的杨辉三角形。如果杨辉三角形的每一行都用一个列表A表示,那么第m行第n列走法数值的所在位置就是列表 A[m+n-1] 的第m个数(或第n个,两者的值相等)。

代码如下:

>>> def YhPaths(m,n):i = m+n-1t = L = [1]while(i>1):i -= 1t = L+[t[n]+t[n+1] for n in range(len(t)-1)]+Lreturn t[m-1]>>> YhPaths(3,2)
3
>>> YhPaths(7,3)
28
>>> YhPaths(7,7)
924
>>> YhPaths(8,8)
3432
>>> YhPaths(9,9)
12870
>>> 

杨辉三角形有很多种解法,稍作变形都可以成为本题的解法,所以方法二可以变化出一个系列的解法。比如,以下这个代码就比较精简:

>>> def YhPaths(m,n):L,n=[1],m+n-2for _ in range(n):L=[sum(_) for _ in zip([0]+L,L+[0])]return L[m-1]

方法三递归法,实用性不是不强,仅作一个递归实例学习。自己写了一个递归,当m、n的小者大于13时,因递归次数太多,用时有点过长了。递归法就是这个缺点:递归次数有限,耗时耗内存。看测试的规律在大于13阶后,阶数每增加1耗用的时间约为前一阶耗时的4倍左右。

>>> def recursion(m,n):if n>m: m,n=n,m  # 特别当m==n时如不交换多耗时50%,但交换错大小会更耗时if n<1: return 0if n==1:return 1if n==2:return mif n==3:return sum(range(m+1))if n==4:return sum([sum(range(i+1)) for i in range(m+1)])return sum([recursion(n-1,i) for i in range(m+1)])>>> recursion(8,8)
3432
>>> recursion(9,9)
12870
>>>
>>> # 时间测试
>>> from time import time
>>> t=time();recursion(14,14);time()-t
10400600
0.6708013248443604
>>> t=time();recursion(15,15);time()-t
40116600
2.4808049488067627
>>> t=time();recursion(16,16);time()-t
155117520
9.5400018148422241
>>> t=time();recursion(17,17);time()-t
601080390
36.812686896324158
>>> t=time();recursion(18,18);time()-t
2333606220
140.20606572151184
>>> 

以下这个代码递归的更彻底,但耗时更厉害。递归法的本质是从终点把未知的答案用表达式往前递推;而前面的方法一、方法二是从起点用已知的结果向后递推;虽然方法不同,但实质用的表达式都是某一格的值等于前一格的值加上一格的值。

>>> def Recursion(m,n):if m==1 or n==1: return 1return Recursion(m,n-1)+Recursion(m-1,n)>>> Recursion(3,7)
28
>>> Recursion(7,3)
28
>>> Recursion(9,9)
12870
>>>
>>> from time import time
>>> t=time();Recursion(14,14);time()-t
10400600
2.5571463108062744
>>> t=time();Recursion(15,15);time()-t
40116600
9.740557193756104
>>> t=time();Recursion(16,16);time()-t
155117520
37.39713907241821
>>> t=time();Recursion(17,17);time()-t
601080390
145.44031858444214
>>> 

方法四优化递归法递归法的速度慢,是因为未知的值重复计算太多;稍加改进,把递归过程中已算过的中间结果存入一个全局变量中,这样已算过的就不再重复计算而是直接调用,可以大幅度提高效率。如下,设置一个列表data,作存储中间结果的全局变量,注意计算前一定要先设置好data的初值:

>>> init = lambda m,n:[[0]*(max(m,n)+1) for _ in range(max(m,n)+1)]
>>> def Recursion(m,n):global dataif m==1 or n==1: return 1elif not data[m][n]:data[m][n]=Recursion(m-1,n)+Recursion(n-1,m)return data[m][n]>>> m,n=3,7
>>> data=init(m,n);Recursion(m,n)
28
>>> m,n=7,3
>>> data=init(m,n);Recursion(m,n)
28
>>> m,n=9,9
>>> data=init(m,n);Recursion(m,n)
12870
>>> from time import time
>>> m,n=500,500
>>> t=time();data=init(m,n);Recursion(m,n);time()-t
676396999362954378167203938002941129870250271387758475994476664430994566
330135620366898107919175100513920435648202067329334859234361060854729465
014263059247806970681340720053443850010209554272633544980383181925937364
977441832552253540042528076643524441731372880722879385596666940182447106
60615920000
0.4687480926513672
>>> # 经过我的电脑测试,当m,n都大于513时就会超出最大递归深度而报错

方法五排列法问题的本质:对m行n列的网格来说,就是n-1个Right和m-1个Down放到一起的排列问题,结果 result = (m-1+n-1)! / [(m-1)! * (n-1)!]。 排列公式也有很多种解法可用:

>>> def perm(n):s=1while n:s*=nn-=1return s>>> def result(m,n):return perm(m+n-2)//perm(m-1)//perm(n-1)>>> m,n = 3,2
>>> result(m,n)
3
>>> m,n = 7,3
>>> result(m,n)
28
>>> m,n = 3,7
>>> result(m,n)
28
>>> 

方法六:组合法,来自于和杨辉三角有关联的二次项定理,其展开项通式: 。对于本题只需要它的系数部分,代入参数后实际的下标为m+n-2,上标为m-1或n-1;其实和方法四一样的,用公式计算的优点就是运行速度超快:

>>> def paths(m,n):ret = 1if n>m: m,n=n,mfor i in range(1,n): ret=ret*(m+n-1-i)//ireturn ret>>> paths(3,7)
28
>>> paths(7,3)
28
>>> paths(9,9)
12870
>>>
>>> paths(1000,1000)
512294053774259558362972111801106814506359401696197357133662490663268680890966422168
317407249277190145438911035517264555381561230116189292650837306095363076178842645481
320822198226994485371813976409676367032381831285411152247284028125396742405627998638
503788368259307920236258027800099771751391617605088924033394630230806037178021722568
614945945597158227817488131642780881551702876651234929533423690387735417418121162690
198676382656195692212519230804188796272372873746380773141117366928488415626459630446
598074332450038402866155063023175006229242447751399777865500335793470023989772130248
615305440000
>>> 

方法七:写成类class的形式,使用时有多种调用方法。注意直接使用 Yh(m,n),它返回的值并不是一个整数,需要用int()强制转换;可以使用示例中的其他调用方法直接取得整数结果。

>>> class Yh():def __init__(self,m=1,n=1):self.data = 1if m!=1: self.data=Yh().N(m,n)def __repr__(self):return f'{self.data}'__str__ = __repr__def __int__(self):return self.valuedef N(self,m,n):self.data = [1]n=m+n-1while n>1:n-=1self.data.append(0)self.data=[sum(z) for z in zip(self.data,self.data[::-1])]return self.data[m-1]@propertydef value(self):return self.data>>> Yh(3,7)
28
>>> Yh(7,3)
28
>>> Yh(9,9)
12870
>>> type(Yh(9,9))
<class '__main__.Yh'>
>>>
>>> int(Yh(7,7))
924
>>> int(Yh(8,8))
3432
>>> int(Yh(9,9))
12870
>>>
>>> Yh(3,7).value
28
>>> Yh(9,9).value
12870
>>> type(Yh(9,9).value)
<class 'int'>
>>>
>>> y = Yh()
>>> y.N(9,9)
12870
>>> y.N(3,7)
28
>>> type(y.N(7,3))
<class 'int'>
>>>
>>> Yh().N(7,3)
28
>>> Yh().N(9,9)
12870
>>> 

小结:上述几种方法中,用组合公式计算是最快的!


加强版 第63题

当地图上的某一点出现障碍物,路径数有什么变化??

方法一:参照前一题方法一的思路,只是到有障碍的地方需要把走法数置为零。先来创建一个地图生成器:地图用来表示m行n列的网格,障碍位置x行y列,有障碍的位置为0,其余都为1(0和1的设置与原题目中的示例相反),如下:

>>> Map = lambda m,n,x,y:[[int((i,j)!=(x-1,y-1)) for j in range(n)] for i in range(m)]
>>> Map(3,3,2,2)
[[1, 1, 1], [1, 0, 1], [1, 1, 1]]
>>> Map(3,7,2,3)
[[1, 1, 1, 1, 1, 1, 1], [1, 1, 0, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1]]
>>> 

然后在此地图的第二行第二列开始照前一题的样子“做加法”,但遇到0要跳过:

特例:当障碍出现在首行(或首列)时,障碍右边(或正下方)的格子都是机器人到不了的地方,所以障碍及其右侧(或下方)的格子都要置数为0,如下图所示:

代码如下:

>>> def Paths(m=3,n=3,x=2,y=2):'''有障碍走法,网格mxn障碍坐标(x,y)'''map = [[int((i,j)!=(x-1,y-1)) for j in range(n)] for i in range(m)]if x==1:map[0][y-1:]=[0]*(len(map[0])-y+1)if y==1:for i in range(x-1,len(map)):map[i][0]=0for i in range(1,m):for j in range(1,n):if map[i][j]!=0:map[i][j]=map[i-1][j]+map[i][j-1]return map[m-1][n-1]>>> def paths(m,n):'''无障碍走法,网格mxn'''r = [[1 for _ in range(m)] for _ in range(n)]for i in range(1,n):for j in range(1,m):r[i][j] = r[i-1][j]+r[i][j-1]return r[n-1][m-1]>>> Paths()
2
>>> Paths(3,7,2,3)
13
>>>
>>> # 障碍出现在首列
>>> Paths(4,7,2,1)
56
>>> paths(4,6)
56
>>> Paths(4,7,3,1)
77
>>> paths(3,6)
21
>>> paths(4,6)+paths(3,6)
77
>>> # 障碍出现在首行
>>> Paths(4,7,1,4)
64
>>> paths(3,7)
28
>>> paths(3,6)
21
>>> paths(3,5)
15
>>> paths(3,7)+paths(3,6)+paths(3,5)
64
>>> # 障碍出现在起点或终点,走法数都为0
>>> Paths(4,7,1,1)
0
>>> Paths(4,7,4,7)
0
>>> 

方法二:解题思路是用无障碍时的走法总数减去经过障碍那一点的走法数;后者等于起点到障碍点的走法数与障碍点到终点的走法数的乘积。调用上一题的paths(m,n),引入障碍坐标x,y表示x行y列有障碍。所求结果即等于:  paths(m,n) - paths(x,y)*paths(m-x+1,n-y+1)

代码如下:

>>> def paths(m,n):r = [[1]*m for _ in range(n)]for i in range(1,n):for j in range(1,m):r[i][j] = r[i-1][j]+r[i][j-1]return r[n-1][m-1]>>> def Paths(m,n,x,y):return paths(m,n) - paths(x,y)*paths(m-x+1,n-y+1)>>> m,n=3,3; x,y=2,2
>>> Paths(m,n,x,y)
2
>>> m,n=3,7; x,y=2,3
>>> Paths(m,n,x,y)
13
>>> 

当问题扩展到有多个障碍物时,凡是有障碍的格子就都置成0估计也能成立(没实测)!

————===== The End =====————

Python 机器人魔鬼的步伐中居然隐藏着杨辉三角形相关推荐

  1. python 机器人聊天_使用python构建您的第一个聊天机器人并将其集成到电报中

    python 机器人聊天 Did you know? 你知道吗? "Chatbots can cut operational costs by up to 30%. eCommerce ch ...

  2. python机器人编程——在VREP环境中,UARM与摄像头联动,实现基于视觉识别的自动抓取,垃圾自动分类(上)

    目录 前言 在VREP中加入视觉传感,用python侧获取图像 在VREP中布置垃圾分类场景 Step1:用三维软件造模型 Step2:导入到vrep Step3:关联shap.调整位置及贴图 测试一 ...

  3. python机器人编程——垃圾自动分类,在VREP环境中,UARM与摄像头联动,实现基于视觉识别的自动抓取(下)

    目录 前言 非绝对垂直摄像头图像的坐标变换 物体的识别和坐标确定 (1)物体的识别 (2)坐标的确定 机械臂执行动作逻辑实现 python源码 前言 本篇我们在<python机器人编程--VRE ...

  4. Python 机器人学习手册:1~5

    原文:ILearning Robotics using Python 协议:CC BY-NC-SA 4.0 译者:飞龙 本文来自[ApacheCN 计算机视觉 译文集],采用译后编辑(MTPE)流程来 ...

  5. Python编程系列教程第13讲——隐藏数据和封装

    视频地址:http://www.56.com/u88/v_OTM5NjU0MjE.html#fromoutpvid=OTM5NjU0MjE 普及网络安全知识,推动信息技术发展. 为祖国的网络安全撑起一 ...

  6. python算法和数据结构_Python中的数据结构和算法

    python算法和数据结构 To 至 Leonardo da Vinci 达芬奇(Leonardo da Vinci) 介绍 (Introduction) The purpose of this ar ...

  7. python编写ATM类_Python中编写类的各种技巧和方法

    有关 Python 内编写类的各种技巧和方法(构建和初始化.重载操作符.类描述.属性访问控制.自定义序列.反射机制.可调用对象.上下文管理.构建描述符对象.Pickling).你可以把它当作一个教程, ...

  8. 定义python函数时如果没有return_定义 Python 函数时,如果函数中没有 return 语句,则默认返回空值 None 。_学小易找答案...

    [多选题]因发现核酶而共享诺贝尔化学奖的科学家是(). [简答题]如果是六角梅花,你还可以用什么方法完成? [填空题]如果函数中没有 return 语句或者 return 语句不带任何返回值,那么该函 ...

  9. python机器人库(robotics-toolbox-python)的运用

    python机器人库(robotics-toolbox-python)的运用 Python 机器人库的运用 文章目录 python机器人库(robotics-toolbox-python)的运用 前言 ...

最新文章

  1. build.xml(黎活明安全管理部署文件)
  2. 机器学习奠基人Michael Jordan:下代技术是融合经济学,解读2项重要进展(含PPT)...
  3. 05-JDBC学习手册:JDBC2.0 新特性之批量处理数据
  4. Linux Network Administrators Guide
  5. Netty网络聊天室完整代码实现
  6. BZOJ4520:[CQOI2016]K远点对(K-D Tree)
  7. sqoop2操作流程
  8. 利用反射机制创建新类的两种方式及比较
  9. 在EL表达式中引用ADF Faces组件
  10. python 矩阵库_NumPy 矩阵库(Matrix)
  11. 数据结构与算法分析(八)——BFS算法
  12. irule jar包无法导入_poi表格的导入导出
  13. 监控系统之weblogic,apache,tuxedo的监控项的整理
  14. 【渝粤教育】电大中专中药制剂学作业 题库
  15. axure轮播图怎么设置循环轮播_Axure教程:广告图片自动轮播+点击切换
  16. [转]innodb的锁时间
  17. html5swf小游戏源码,亲测可用120个H5小游戏实例源码
  18. 怎样快速修改论文格式-使用人工智能技术助手
  19. android app红点提示,技巧|手机提示软件更新的小红点逼得我强迫症犯了,怎么去掉提示?...
  20. 投影向量=投影长度乘以单位向量

热门文章

  1. 向他们看齐,做csdn MVP
  2. Timeout expired after 60000milliseconds while awaiting InitProducerId
  3. 如何下载西长安街街道卫星地图高清版大图
  4. 基于bootstrap的前端分页。带省略号和上下页。
  5. 【Vuejs】971- Vue SSR 性能优化实践
  6. Flutter仿钉钉打卡
  7. toString如何转json
  8. java内存溢出和内存泄漏的区别
  9. Ubuntu16,JAVA JNA调用fortran,Fortran编译器:Intel Fortran, Fortran77版本
  10. 用英文帮我写一篇关于vintage stationery papers 的listing,包括标题,五点描述,长描述,以及关键词...