一天一道演算法題 之動態規劃

2022-08-05 09:24:18 字數 3265 閱讀 1283

最近做了幾道動態規劃題,發現了其中一些規律,認真覆盤一下。

先來看幾道題。

【1 機械人走方格】有乙個xxy的網格,乙個機械人只能走格點且只能向右或向下走,要從左上角走到右下角。請設計乙個演算法,計算機械人有多少種走法。注意網格中有些障礙點是不能走的。給定乙個intmap(c++ 中為vector >),表示網格圖,若map[i][j]為1則說明該點不是障礙點,否則則為障礙。另外給定intx,inty,表示網格的大小。請返回機械人從(0,0)走到(x - 1,y - 1)的走法數。

【2 洪水】在乙個nxm矩陣形狀的城市裡爆發了洪水,洪水從(0,0)的格仔流到這個城市,在這個矩陣中有的格仔有一些建築,洪水只能在沒有建築的格仔流動。請返回洪水流到(n - 1,m - 1)的最早時間,(洪水只能從乙個格仔流到其相鄰的格仔且洪水單位時間能從乙個格仔流到相鄰格仔)。

【3 硬幣表示】有數量不限的硬幣,幣值為25分、10分、5分和1分,請編寫**計算n分有幾種表示法。給定乙個intn,請返回n分有幾種表示法。

以上幾道題都有乙個共同點就是有很多種可能,並且每一步都取決於上一步的結果,整個過程是動態的。

這個過程其實和遞迴有點像,但是過程還是不一樣的。

動態規劃就是:將原問題拆解成若干子問題,同時儲存子問題的答案,使得每個子問題只求解一次,最終獲得原問題的答案。

遞迴是:要解決原問題,先解決比原問題規模更小的子問題。解決了小問題,再回過頭來獲得最終原問題的答案。

動態規劃是顯式得把每一步的結果儲存下來,而遞迴則是把每一子步的結果存在記憶體中,一呼叫完成就回銷毀,因此同乙個子問題可能會被求解多次。

因此動態規劃本質就是用空間換時間。

分為以下三步:

1 找到初始解。

2 找到規律,對於任意的i,如何通過之前的解推斷出來?

3 找到遍歷路徑

4 迴圈

我們用乙個dp陣列用來儲存走到每乙個格仔的走法數,題中要求的第(x-1, y-1)格仔的走法數就是dp[x-1][y-1]。

1 找到初始解

dp[0][0]=1,因為只有一種解法。

2 找到規律。

由於題中規定,只能從左往右或從上往下,對於任意的i和j,要走到dp[i][j],只能從dp[i-1][j] 或 dp[i][j-1]走過去。因此dp[i][j] = dp[i-1][j] + dp[i][j-1]。

當i==0或j==0時, dp[0][j] = dp[0][j-1], dp[i][0] = dp[i-1][0]。

另外,當map[i][j]不等於1時,表示此格不通。此時保留dp[i][j]=0即可,表示沒有方法可以到達。

3 找到路徑

為了得到最後乙個格仔數,逐行遍歷即可。

4 迴圈

int countways(vectorint> > map, int x, int

y) }

return dp[x-1][y-1

];}

同樣用陣列dp[i][j]用來表示到達該格仔的最短時間。

1 找到初始解

洪水從第乙個格仔開始,因此dp[0][0] = 0。

2 找到規律

對於任意格仔ij來說,洪水可以從四個方向過來。第一反應就是周圍四個格仔的值取最小即可,即dp[i][j] = min(dp[i-1][j], dp[i+1][j], dp[i][j-1], dp[i][j+1]) + 1。 但是真正執行起來可以發現,周圍四個格仔值其實也取決於ij這個格仔的值本身,也就是說這五個格仔之間的值是耦合在一起的。因此,不能直接用上述最小的式子得到。

於是可以換種思路,雖然無法從周圍的格仔得到ij的,但是假如已知dp[i][j]的話,其餘四個格仔的值都可以得到,就是dp[i][j] + 1。這樣不斷更新每個格仔的值,取最小的即可。

3 遍歷路徑

由於洪水可以往四個方向流動,因此不能像之前那樣逐行遍歷,因為在遍歷過程中後面的值極有可能改變前面的值。因此這裡借助乙個佇列來遍歷全部的格仔。

每遍歷乙個格仔,就把上下左右四個格仔分別加入到佇列中。事先給定的map中格仔的值為1表示有建築,假如格仔的值為0表示沒建築。假如遍歷過,那麼格仔的值就不再是0。因此可以通過dp[i][j]來判斷是否要將它加入佇列。

4 迴圈

int floodfill(vectorint> > map, int n, int

m),,,};

que.push(0);

while(!que.empty())}}

return0;

}

再看最後一題,【硬幣表示】1 找到初始解當 n=1,那麼只有1種解法,只能用1表示。

2 找到規律

對於n來說,對於coin = 5,dp[n] =dp[n] + dp[n-5], 同理,對於coin =10, dp[n] = dp[n] + dp[n-10] 。

3 遍歷路徑

這裡其實存在有兩個維度來遍歷,乙個是不斷增長的n,另乙個是幣種數。

假如只有一種幣種,例如1,那麼可以求解每乙個n的組合方式(都是1)。每增加一種幣種,就多了一種新的表示方式,把每一種幣種表示方式加起來其實就是最終的結果。

4 迴圈

int countways(intn);

int dp[100001] = ;

dp[0] = 1

;

for(int i=0;i<4;i++)

return

dp[n];

}

暫時總結到這裡,以後還有新的補充再寫。

一天一道演算法題 樹狀陣列

題目 模板 樹狀陣列1 樹狀陣列和線段樹差不多,可以處理區間操作,但是處理不了太複雜的區間問題。不過 比線段樹簡潔很多很多!時間複雜度都為o logn 例如,區間 1,8 儲存方式如下 1 tree 1 num 1 001 001 2 tree 2 num 2 num 1 010 010 001 3...

一天一道演算法題 線段樹

題目 模板 線段樹1 rmq問題 range minimum maximum query 和求區間和的問題可以用暴力法做,時間複雜度為o n 2 用在本題會超時,所以我們選擇線段樹做。線段樹是一種用於區間操作的資料結構,用二叉樹構造。如圖。線段樹的每個節點代表了乙個區間。防止超時,用了lazy標記。...

一天一道演算法題 5 24 遞迴

我們每一天都應該比昨天更強一點 觀察下列式子 12 12 1 12 6 2 12 4 3 12 3 4 12 3 2 2 12 2 6 12 2 3 2 12 2 2 3 對於給定的n 計算n公有多少種不同的分解式?1 include 2 using namespace std 34 int cnt...