最近在刷力扣上的題目,刷到了65不一樣路徑,當初上大學的時候,曾在hihocoder上刷到過這道題目,可是如今已經幾乎全忘光了,大概的知識點是動態規劃,現在就讓咱們一塊兒來回顧一下。 數組
題目原文是:優化
一個機器人位於一個 m x n 網格的左上角 (起始點在下圖中標記爲「Start」 )。spa
機器人每次只能向下或者向右移動一步。機器人試圖達到網格的右下角(在下圖中標記爲「Finish」)。code
問總共有多少條不一樣的路徑?cdn
例如,上圖是一個7 x 3 的網格。有多少可能的路徑?blog
說明:m 和 n 的值均不超過 100。圖片
示例 1:leetcode
>輸入: m = 3, n = 2get
>輸出: 3數學
>解釋:
>從左上角開始,總共有 3 條路徑能夠到達右下角。
>1. 向右 -> 向右 -> 向下
>2. 向右 -> 向下 -> 向右
>3. 向下 -> 向右 -> 向右
示例 2:
>輸入: m = 7, n = 3
>輸出: 28
咱們先按照正常思路來想一下,當你處於起點時,你有兩個選擇,向右或者向下,除非你處於最下面一排或者最右邊一列,那你只有一種選擇(好比處於最下面一排,你只能往右),其餘位置,你都有兩種選擇。
所以,咱們就根據這個思路,能夠寫出代碼:
class Solution {
public int uniquePaths(int m, int n) {
// 特殊狀況:起點即終點
if (m == 1 && n == 1) {
return 1;
}
// 當前處於(1,1),終點爲(m,n)
return walk(1, 1, m, n);
}
public int walk(int x, int y, int m, int n){
// 已經處於終點
if (x >= m && y >= n) {
return 0;
}
// 處於最下面一排或者最右邊一列
if (x >= m || y >= n) {
return 1;
}
// 往下走,有多少種走法
int down = walk(x, y + 1, m, n);
// 往右走,有多少種走法
int right = walk(x + 1, y, m, n);
// 從當前(x,y)出發,走到(m,n),共有多少種走法
return down + right;
}
}複製代碼
咱們考慮一下,這種寫法,有沒有能夠優化的地方。
大家應該一眼就發現,walk
方法的第一個判斷if (x >= m && y >= n)
,永遠都不可能爲true
,由於下一個判斷if (x >= m || y >= n)
就已是臨界點狀況,直接就已經有返回值,根本不可能達到x >= m && y >= n
的狀況。所以,該判斷能夠刪除。
假設咱們從(1,1)的位置出發,終點是(3,3),那麼到達(2,2)這個中間點的話有幾種走法呢?兩種,先到(1,2)再到(2,2),或者,先到(2,1)再到(2,2)。
所以,若是根據咱們上面的寫法,從(2,2)到終點(3,3),咱們會算兩次,雖然這樣的思路自己是正確,但這樣的狀況應該是能夠優化的。由於從(1,1)到(3,3),一共只有6種路徑,但已經有2條是重複的路徑了,那麼隨着m
與n
愈來愈大,中間點會愈來愈多,那麼重複的路徑也會愈來愈多。
這就是前面的選擇
對於後面的選擇
會有影響,即便後面的選擇
相同,但因爲前面的選擇
不一樣,從而也被認爲是不一樣的選擇。
很明顯,後面的選擇
更加惟一,若是咱們先在後面作出選擇,那麼就能夠減小重複計算的次數。所以,咱們能夠試試反向思路。
若是咱們不是從起點出發,而是從終點倒退到起點開始算的話。假設終點是(3,3),它只能由(2,3)和(3,2)直接到達,(2,3)也只能由(2,2)和(1,3)直接到達,(1,3)只能由(1,2)直接到達,(1,2)只能由(1,1)直接到達,所以(1,3)只能由(1,1)直達。
咱們能夠得出規律:除了最左邊一列和最上面一排的點,只能由起點(1,1)直達之外,其餘的點(x,y)都是由(x-1,y)和(x,y-1)兩個點直接到達的。
所以,根據這個思路,咱們能夠寫出代碼:
class Solution {
public int uniquePaths(int m, int n) {
int[][] result = new int[m][n];
int j;
for (int i = 0; i < m; i++) {
for (j = 0; j < n; j++) {
if (i == 0 || j == 0) {
// 最上面一排的點和最左邊一列的點,只能由(1,1)到達
result[i][j] = 1;
} else {
// 其餘的點均可以由左邊的點和上面的點到達
result[i][j] = result[i - 1][j] + result[i][j - 1];
}
}
}
return result[m - 1][n - 1];
}
}複製代碼
其實這樣的想法就已是動態規劃
的範疇了,咱們看看維基上的定義
動態規劃(英語:Dynamic programming,簡稱DP)是一種在數學、管理科學、計算機科學、經濟學和生物信息學中使用的,經過把原問題分解爲相對簡單的子問題的方式求解複雜問題的方法。
一開始我感受很像分治法
,由於都須要將一個大問題分解爲子問題,但分治法
最終會將子問題合併,但動態規劃
卻不用。
咱們考慮一下,這種寫法,有沒有能夠優化的地方。
首先是空間上的優化,咱們必定要用二維數組嗎?能夠用一維數組代替嗎?
答案是確定的,由於每一個點的計算只和左邊與上邊相鄰的點有關,所以,不須要更加久遠的點。
假如只用一維數組,那麼只須要存儲上一排的結果,若是計算到下一排的時候,則依次替換,代碼爲:
class Solution {
public int uniquePaths(int m, int n) {
int[] dp = new int[m];
int j;
for(int i = 0; i < n; i++) {
for(j = 0; j < m; j++) {
if(j == 0) {
dp[j] = 1;
}
else {
// 其餘的點均可以由左邊的點和上面的點到達
dp[j] += dp[j-1];
}
}
}
return dp[m-1];
}
}複製代碼
這樣的優化,差很少就結束了。那咱們是否能夠從思路上進行優化呢?
由於咱們只有向右或向下兩種選擇,而咱們一共要走的路徑實際上是(m-n-2)
,其中有(m-1)
的路徑是向右,(n-1)
的路徑是向下,其實能夠轉變爲:
從
(m-n-2)
中挑出(m-1)
,即組合數C((m-n-2), (m-1))
的值
那麼咱們能夠寫出代碼:
class Solution {
public int uniquePaths(int m, int n) {
// 用double,由於計算出的數值會很大
double num = 1, denom = 1;
// 找出更小的數,這樣能夠減小計算次數和計算出的數值
int small = m > n ? n : m;
for (int i = 1; i <= small - 1; ++i) {
num *= m + n - 1 - i;
denom *= i;
}
return (int)(num / denom);
}
}複製代碼
以上就是我作這道題的一些思路和想法了,雖然題目自己不難,但能夠討論的點仍是不少的,若是你們有什麼疑問,歡迎在下方留言。有興趣的話能夠關注個人公衆號,說不定會有意外的驚喜。