在公司個一次team building中 馬小哈同窗提出了一個問題。python
問題描述:算法
棋盤被分紅n*n的格子,每一個格子有若干米粒,有一隻小雞從左上角出發,移動到右下角,每次只能向右或者向下移動。 編程
求一個算法,算法的輸入是每一個格子的米粒數,輸出是一個「向右走」/「向下走」的指令序列,使得小雞吃到的米粒數最大化。 app
不正真的紳士,很快給出求解, 博君一樂。優化
解答:ui
這題是一箇中等難度的二維動態規劃題。
(動態規劃是一種有趣的解題技術,用來解運籌學中的最優化問題。有一類問題,用遞歸、回溯或者窮舉來解會致使時間複雜度爆炸,而用貪心又可能陷入局部最 優。用動態規劃解它 只須要多項式時間,且獲得的是全局最優。用動態規劃,通常把原問題劃分紅階段,而且每一個階段都是一個一個同類型的可是規模更小的問題,從當前階段作一個決 策後,就到了下一個階段。首先,把從一個階段到下一個階段的狀態轉移狀況用一個「狀態轉移方程」寫出來。而後從最小的問題(也就是最終的階段)出發,倒回 頭來推原問題的解,把狀態轉移方程變化成一個規劃方程,這樣就把一個遞歸的問題變化成了遞推的問題。解題原理和遞歸算法很像,區別是動態規劃算法裏得由程 序員維護一個表來保存和查詢計算的中間狀態,而且要手動的把遞歸問題變化成遞推問題,所以動態規劃比遞歸快不少,是一種用空間換時間的技術。建模每每很費 腦子,可是按照規劃方程寫出來的程序每每很短。)spa
推薦一本神書《How to Solve It》,須要的數學基礎爲0,講解各類解題技巧,中間穿插各類趣味故事和智力題,完暴《編程之美》,曾經在不正真的紳士幼小的心靈中留下不可磨滅的痕跡,實爲茶餘飯後放鬆類讀物的上佳選擇。
英國女婿版 http://book.douban.com/subject/1456890/
踹你斯版 http://book.douban.com/subject/2124114/遞歸
舉個例子,假設有一個5*5的矩陣 get
3 5 5 2 1 數學
4 8 9 0 3
5 6 7 1 2
5 6 3 4 8
9 3 2 1 4
從(1,1)走到(5,5),每次只能向右或向下移動,累計走過的數字,求最大化累計數字的走法。
假設如今在(1,1),當前數字是3,可能的移動是到(1,2)的5或者(2,1)的4。然而,若是向右移動,就不再可能吃到左下角的9了,彷佛向下移動更好。移動到(2,1),當前是4,若是向下移動,就吃不到右邊的8和9了,若是向右移動,就吃不到左下角的9了,彷佛往右移動收益才更大。然而若是這回往右移動,就和剛纔的第一步移動的理由矛盾了,第一步要往下移動的理由是要吃左下角的9,可是如今往右的話,就根本沒去吃它,那第一步就不必往下移動去吃4了,而是應該往右移動吃5纔對。實際上,這個陣的最優移動方式是,3 右 5 下 8 右 9 下 7 下 3 右 4 右 8 下,最大收益是51,並無吃到左下角的9。
逆向思考,從右下角的終點開始思考。如今從原矩陣的右下角開始劃,分出一個1*1的子矩陣,
3 5 5 2 1
4 8 9 0 3
5 6 7 1 2
5 6 3 4 8
---
9 3 2 1 |4
假設咱們走在這個子陣的左上角,要到右下角而且積累數字最大化,顯然只有一種走法,最多能積累的數字和爲4。
如今從原矩陣的右下角開始劃,分出一個2*2的子矩陣,
3 5 5 2 1
4 8 9 0 3
5 6 7 1 2
----
5 6 3 |4 8
9 3 2 |1 4
單獨考察這個子陣,假設當前在數字8的位置,所以下一步只能往下走,最終收益12,假設在數字1的位置,下一步只能往右,最終收益5。假設在4的位置,能夠往右或者往下,往右的最終收益是4 + 12 = 16,比較大,因此應該往右。
如今,咱們就獲得了一個2*2的收益矩陣。
16 12
5 4
這個收益矩陣的每一個元素表示從剛纔劃出的子矩的對應的某格子出發,可能吃掉的數字之和的最大值。
如今從原矩陣的右下角開始劃,分出一個3*3的子矩陣,
3 5 5 2 1
4 8 9 0 3
------
5 6 |7 1 2
5 6 |3 4 8
9 3 |2 1 4
對應的收益矩陣是,
? ? ?
? 16 12
? 5 4
和剛纔的計算方法同樣,首先算右上角,等於子陣的右上角元素2加上其下元素8所可能帶來的最大收益,而這個最大收益已經在2*2的收益矩陣裏算過了,爲12。因此收益陣的右上角是2 + 12 = 14。
? ? 14
? 16 12
? 5 4
收益陣的元素(1,2)應該等於子陣的元素(1,2) + max{收益陣(2,2)=16, 收益陣(1,3)=14},結果爲17。
? 17 14
? 16 12
? 5 4
接着就按照相似的道理把3*3的收益陣算完,獲得
26 17 14
19 16 12
7 5 4
接着再用一樣的辦法算4*4的子陣和收益陣,一直算下去。最後獲得5*5的收益陣以下:
51 48 40 20 18
47 43 35 17 17
37 32 26 17 14
30 25 19 16 12
19 10 7 5 4
收益陣的每一個元素表明了從原矩陣對應元素開始走,可能吃到的數字和的最大值。所以,在舉例的5*5矩陣中,能吃到的數字和最大是51。如今就能夠很簡單的根據收益陣來輸出小雞行動的指令,從左上角開始,選數字大的格子往右或者往下走就好了。
Python程序chicken.py:
def init_nn_matrix(n):
''' Return an n*n zeroed matrix. '''
return [[0] * n for i in range(0, n)]
def potential_gain(m):
''' Calculate max potention gain for each grid. '''
n = len(m) # assume m is an n*n matrix
r = init_nn_matrix(n)
r[n-1][n-1] = m[n-1][n-1]
for s in range(2, n + 1):
# deal with an s*s sub-matrix
si = sj = n - s
r[si][n-1] = m[si][n-1] + r[si+1][n-1]
for j in range(n - 2, sj, -1):
r[si][j] = m[si][j] + max(r[si][j+1], r[si+1][j])
r[n-1][sj] = m[n-1][sj] + r[n-1][sj+1]
for i in range(n - 2, si, -1):
r[i][sj] = m[i][sj] + max(r[i+1][sj], r[i][sj+1])
r[si][sj] = m[si][sj] + max(r[si][sj+1], r[si+1][sj])
return r
def walk_matrix(gain):
directions = []
n = len(gain)
i = j = 0
while i != n - 1 or j != n - 1:
if i == n - 1:
directions.append('-')
j = j + 1
continue
if j == n - 1:
directions.append('|')
i = i + 1
continue
if gain[i+1][j] > gain[i][j+1]:
directions.append('|')
i = i + 1
else:
directions.append('-')
j = j + 1
return directions
def solve_matrix(m):
print 'matrix'
for row in m:
for col in row:
print col,
gain = potential_gain(m)
print 'potential gain matrix'
for row in gain:
for col in row:
print col,
print "Directions:"
print ' '.join(walk_matrix(gain))
print 'Total:', gain[0][0]
if __name__ == "__main__":
m = [[3, 5, 5, 2, 1, ],
[4, 8, 9, 0, 3, ],
[5, 6, 7, 1, 2, ],
[5, 6, 3, 4, 8, ],
[9, 3, 2, 1, 4, ], ]
solve_matrix(m)
m = [[0, 0, 0, 0, 0, ],
[0, 0, 0, 0, 0, ],
[0, 0, 0, 0, 0, ],
[0, 0, 0, 0, 0, ],
[0, 0, 0, 0, 0, ], ]
solve_matrix(m)
m = [[1, 0, 0, 0, 0, ],
[0, 1, 0, 0, 0, ],
[0, 0, 1, 0, 0, ],
[0, 0, 0, 1, 0, ],
[0, 0, 0, 0, 1, ], ]
solve_matrix(m)
m = [[1, 1, 1, 1, 1, ],
[0, 0, 0, 0, 1, ],
[0, 0, 0, 0, 1, ],
[0, 0, 0, 0, 1, ],
[0, 0, 0, 0, 1, ], ]
solve_matrix(m)
m = [[1, 0, 0, 0, 0, ],
[1, 0, 0, 0, 0, ],
[1, 0, 0, 0, 0, ],
[1, 0, 0, 0, 0, ],
[1, 1, 1, 1, 1, ], ]
solve_matrix(m)
m = [[3, 4, 5, 2, 1, ],
[4, 8, 9, 0, 3, ],
[5, 6, 7, 1, 2, ],
[5, 6, 3, 4, 8, ],
[99, 3, 2, 1, 4, ], ]
solve_matrix(m)
運行
$ python chicken.py
輸出結果中,「-」表示向右移動,「|」表示向下移動。
matrix
3 5 5 2 1
4 8 9 0 3
5 6 7 1 2
5 6 3 4 8
9 3 2 1 4
potential gain matrix
51 48 40 20 18
47 43 35 17 17
37 32 26 17 14
30 25 19 16 12
19 10 7 5 4
Directions:
- | - | | - - |
Total: 51
matrix
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
potential gain matrix
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
Directions:
- - - - | | | |
Total: 0
matrix
1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 1 0
0 0 0 0 1
potential gain matrix
5 4 3 2 1
4 4 3 2 1
3 3 3 2 1
2 2 2 2 1
1 1 1 1 1
Directions:
- | - | - | - |
Total: 5
matrix
1 1 1 1 1
0 0 0 0 1
0 0 0 0 1
0 0 0 0 1
0 0 0 0 1
potential gain matrix
9 8 7 6 5
4 4 4 4 4
3 3 3 3 3
2 2 2 2 2
1 1 1 1 1
Directions:
- - - - | | | |
Total: 9
matrix
1 0 0 0 0
1 0 0 0 0
1 0 0 0 0
1 0 0 0 0
1 1 1 1 1
potential gain matrix
9 4 3 2 1
8 4 3 2 1
7 4 3 2 1
6 4 3 2 1
5 4 3 2 1
Directions:
| | | | - - - -
Total: 9
matrix
3 4 5 2 1
4 8 9 0 3
5 6 7 1 2
5 6 3 4 8
99 3 2 1 4
potential gain matrix
126 47 40 20 18
123 43 35 17 17
119 32 26 17 14
114 25 19 16 12
109 10 7 5 4
Directions:
| | | | - - - -
Total: 126