背景:還是秋招刷題
參考:https://www.bilibili.com/video/av57064859?from=search&seid=769707923257914088 (B站果然是學習的網站)
題目:一個M*N網格,從左下角(0,0)出發,走到右上角。只能向上走和向右走,有多少條路徑?
一、無障礙物版本
解題思路:
不管怎樣,都要往上走M-1次,往右走N-1次,且一共一定要走N+M-2次
也就是總共選擇機會有N+M-2次,其中選M-1次往上走,剩下的是往右走,即C(N+M-2,M-1),當然也可以C(N+M-2,N-1)。
排列組合公式:
排列A(m,n)=m×(m-1).(m-n+1)=m!/(m-n)!(m爲下標,n爲上標) 從m箇中取n個的所有排列
組合C(m,n)=P(m,n)/P(n,n) =m!/n!(m-n)!;從m個取n個,不用排列
代碼如下:
def barrier_free_all_ways(M,N):
f=lambda n:1 if n==0 else n*f(n-1) #n是形參,如果n=0則返回1,否則返回n*f(n-1) #這一句是求階乘,複雜度爲O(n)
C=lambda p,q:f(p)/f(q)/f(p-q) #這一句是C的公式
return C(M+N-2,M-1)
#print(barrier_free_all_ways(3,3))
二、有一個障礙物版本
假設障礙物座標爲(x,y)
解題思路:
有效走法=所有走法-經過障礙物的走法
=C(M+N-2,M-1)-把(x,y)看作終點任走 * (x,y)到(M-1,N-1)任走
=C(M+N-2,M-1)-C(x+y,x)*C(M+N-x-y-2,M-x-1)
代碼如下:
def barrier_one_all_ways(M,N,x,y):
f=lambda n:1 if n==0 else n*f(n-1)
C=lambda p,q:f(p)/f(q)/f(p-q)
return C(M+N-2,M-1)-C(x+y,x)*C(M+N-x-y-2,M-x-1) #最終複雜度爲O(max{m,n})或O(m+n) #可不可以寫成O(max{m,n},m+n})???
#print(barrier_one_all_ways(3,3,1,1))
三、有多個障礙物版本
假設障礙物座標爲一個元組構成的列表list[(x1,y1),(x1,y1)…(xn,yn)]
思路一:暴力遞歸
從起點開始,遞歸計算下一步往上和往右走到終點分別有多少種方案,加起來。
特殊情況:
1)當前點是障礙物,返回0種方案
2)當前點是終點,返回1種方案
3)遞歸中走到了上邊框或右邊框且不是終點,那就分別只能繼續往右走和往上走
代碼如下:
def barrier_many_all_ways_rec(M,N,m,n,list): #m,n是當前點
if (m,n) in list:return 0
if m==M-1 and n==N-1:return 1
if m==M-1 and not n==N-1:return barrier_many_all_ways_rec(M,N,m,n+1,list)
if not m==M-1 and n==N-1:return barrier_many_all_ways_rec(M,N,m+1,n,list)
return barrier_many_all_ways_rec(M,N,m,n+1,list)+barrier_many_all_ways_rec(M,N,m+1,n,list)
print(barrier_many_all_ways_rec(9,9,0,0,[(1,1),(3,5),(6,8)]))
優化:也可以把起點和終點換一下,即從右上角到左下角,這樣可以少兩個變量
思路二:動態規劃
建一個全爲0二維數組來存放從起點到當前點有多少種方案。遍歷這個二維數組,起點設爲1。遍歷到終點則結束。
每次遍歷中判斷,如果當前點是障礙物,那麼數組中的值爲0;如果當前點不是障礙物,則數組值爲其左點的方案數+其下點的方案數之和
特殊情況:
如果當前點在下邊框或左邊框上,那麼分別只加其左邊的和其下邊的
代碼如下:
def barrier_many_all_ways_dp(M,N,list):
arr = [[0] * N] * M
arr[0][0]=1
for m in range(len(arr)):
for n in range(len(arr[0])):
if (m,n) in list:
arr[m][n]=0
elif m!=0 and n==0:
arr[m][n] = arr[m-1][n]
elif m==0 and n!=0:
arr[m][n] = arr[m][n-1]
else:
arr[m][n]=arr[m][n-1]+arr[m-1][n]
return arr[M-1][N-1] #其實可以輸出任意位置的方案數
print(barrier_many_all_ways_dp(9,9,[(1,1),(3,5),(6,8)]))
優化:整個矩陣初始化爲1,障礙處置0
其他思路:排列組合+遞歸