二維矩陣從左上角到右下角路徑的最小和

問(wèn)題:LeetCode第64題-求解最小路徑和

給定一個(gè)二維數(shù)組m行作煌,n列掘殴。假設(shè)這個(gè)二維數(shù)組中每個(gè)單元格的值都是正整數(shù),表示從該單元格經(jīng)過(guò)時(shí)需要花費(fèi)的成本粟誓。
那么求從左上角第一個(gè)單元格到右下角最后一個(gè)單元格的路徑中奏寨,花費(fèi)的最小成本是多少?
約束條件為:每次只能向下或者向右移動(dòng)一步鹰服。

說(shuō)明:

給定一個(gè)m=3病瞳,n=3的二維數(shù)組cost揽咕,如下圖所示,從左上角的第一個(gè)單元格出發(fā)仍源,可以向右心褐,向下走,最終達(dá)到右下角笼踩。因?yàn)槁窂?1→3→1→1→1 的總和最小,所以輸出的結(jié)果為7
圖例.png

題解

  • 暴力遞歸算法
  • 記憶化搜索算法
  • 二維動(dòng)態(tài)規(guī)劃算法
  • 一維動(dòng)態(tài)規(guī)劃算法

1. 暴力遞歸

利用遞歸逗爹,對(duì)于每個(gè)元素我們考慮兩條路徑,向右走和向下走嚎于,在這兩條路徑中挑選路徑權(quán)值和較小的一個(gè)掘而。遞推公式如下:
cost(i,j) = grid[i][j] + min(cost(i+1,j),cost(i,j+1))

實(shí)現(xiàn)代碼如下:

/**
 @name 暴力遞歸法
 @brief
   遞歸公式:
  cost(i,j) = grid[i][j] + min(cost(i+1,j),cost(i,j+1))
 以當(dāng)前點(diǎn)為原點(diǎn),依次向右和向下計(jì)算代價(jià)值
 */
int calculate(vector<vector<int>> &grid,int i,int j) {
    size_t bound_m = grid.size();  //行
    size_t bound_n = grid[0].size(); //列
    if (i == bound_m || j == bound_n) return INT_MAX;  //右邊界,下邊界
    if (i == (bound_m -1) && j == (bound_n -1)) return grid[i][j];   //到目的地右下角直接返回
    return grid[i][j] + min(calculate(grid, i+1, j), calculate(grid, i, j+1));  //遞歸查找最短路徑
}

int DPAlgorithm::minPathSum(vector<vector<int>> &grid) {
    //從0,0點(diǎn)開(kāi)始出發(fā)
    return calculate(grid, 0, 0);
}
復(fù)雜度以及優(yōu)化的點(diǎn)

復(fù)雜度
時(shí)間復(fù)雜度:O(2 m+n)于购。每次移動(dòng)最多可以有兩種選擇袍睡。
空間復(fù)雜度:O(m+n))。遞歸的深度是 m+n
優(yōu)化的點(diǎn)
當(dāng)我們從左上角(0,0)開(kāi)始出發(fā)的時(shí)候肋僧,因?yàn)橹荒苎刂?code>右和2個(gè)方向斑胜,所以走1->3 或者1->1,這樣依次走下去,必然會(huì)經(jīng)過(guò)2次5嫌吠,例如:1->3->5...以及1->1>5...止潘,可以想到,當(dāng)走的5的時(shí)候辫诅,5的走法也只有2種走法凭戴,所以這樣就會(huì)造成從這個(gè)點(diǎn)出發(fā)到右下角(2,2)被計(jì)算了2次,如果這個(gè)矩陣很大的話(huà)炕矮,那么這樣的點(diǎn)就會(huì)有很多么夫,就會(huì)被重復(fù)很多次,時(shí)間復(fù)雜度不言而喻肤视。如何優(yōu)化档痪?

LeetCode提交結(jié)果:
暴力遞歸提交結(jié)果.png

2. 記憶化搜索算法

在暴力遞歸的基礎(chǔ)上進(jìn)行優(yōu)化,目的是將每次遞歸計(jì)算后的結(jié)果保存起來(lái)邢滑,下次遞歸計(jì)算的時(shí)候先去查表腐螟,檢查是否有保存過(guò)的計(jì)算結(jié)果,如果有直接返回殊鞭,如果沒(méi)有計(jì)算后更新表。

實(shí)現(xiàn)代碼如下:

//申請(qǐng)一張二維表
static vector<vector<int>> table;

int calculate2(vector<vector<int>> &grid,int i,int j) {
    size_t bound_m = grid.size();  //行
    size_t bound_n = grid[0].size(); //列
    //判斷邊界
    if (i == bound_m || j == bound_n) return INT_MAX;
    //到達(dá)目的地尼桶,直接返回
    if (i == (bound_m -1) && j == (bound_n -1)) return grid[i][j];
    //查表操灿,不為空直接返回結(jié)果
    if (table[i][j] != -1) return table[i][j];
    //結(jié)果求和并存表
    int sum = grid[i][j] + min(calculate2(grid, i+1, j), calculate2(grid, i, j+1));
    table[i][j] = sum;
    return sum;
}

int DPAlgorithm::minPathSum(vector<vector<int>> &grid) {
    //從0,0點(diǎn)開(kāi)始出發(fā)
    //暴力遞歸
//    return calculate(grid, 0, 0);
//    記憶搜索算法
    size_t m = grid.size();  //行
    size_t n = grid[0].size(); //列
    //初始化m*n表的大小以及內(nèi)容
    table = vector<vector<int> >(m,vector<int> (n,-1));
    return calculate2(grid, 0, 0);
    
}
復(fù)雜度以及優(yōu)化的點(diǎn)

復(fù)雜度
時(shí)間復(fù)雜度:O(mn)。
空間復(fù)雜度:O(m
n)泵督。
優(yōu)化的點(diǎn)
通過(guò)使用記憶搜索算法進(jìn)行優(yōu)化趾盐,時(shí)間復(fù)雜度已經(jīng)被顯著的降低了,但空間復(fù)雜度卻顯著的增加了,為了進(jìn)一步降低算法的復(fù)雜度救鲤,我們繼續(xù)進(jìn)行優(yōu)化久窟。

LeetCode提交結(jié)果:
記憶化搜索提交結(jié)果.png

3. 二維動(dòng)態(tài)規(guī)劃算法

新建一個(gè)額外的dp矩陣,與原矩陣大小相同本缠。在這個(gè)矩陣中斥扛,dp(i,j)表示從左上角到[i,j]的最小路徑和。我們根據(jù)左上角的值丹锹,很容易初始化出第一行第一列中 dp值對(duì)應(yīng)的原矩陣的值稀颁。然后根據(jù)初始化的值,去更新整個(gè)dp矩陣的值楣黍,對(duì)于每個(gè)元素[i,j]考慮其由左邊和上邊的值計(jì)算得出匾灶,因此獲得最小路徑有如下遞推公式:
dp(i,j)=grid(i,j) + min(dp(i-1,j),dp(i,j-1))

dp矩陣更新過(guò)程如下圖:
dp矩陣更新過(guò)程.png

從上圖可以看到,當(dāng)我們初始化dp矩陣之后租漂,剩下的操作根據(jù)遞推公式依次填充每一行和每一列阶女,dp右下角便是我們要求的結(jié)果。

實(shí)現(xiàn)代碼如下:

int DPAlgorithm::minPathSum(vector<vector<int>> &grid) {
    //從0,0點(diǎn)開(kāi)始出發(fā)
    //暴力遞歸
//    return calculate(grid, 0, 0);
//    記憶搜索算法
//    size_t m = grid.size();  //行
//    size_t n = grid[0].size(); //列
//    //申請(qǐng)一張m*n的表
//    table = vector<vector<int> >(m,vector<int> (n,-1));
//    return calculate2(grid, 0, 0);
    //二維dp算法
    size_t m = grid.size();  //行
    size_t n = grid[0].size(); //列
    vector<vector<int>> dp(m,vector<int>(n,-1));
    dp[0][0] = grid[0][0];
    //更新第一行
    for (int i = 1; i < n; i++) {
        dp[0][i] = grid[0][i] + dp[0][i-1];
    }
    //更新第一列
    for (int i = 1; i < m; i++) {
        dp[i][0] = grid[i][0] + dp[i-1][0];
    }
    //根據(jù)遞推公式,按行更新dp表
    for (int i = 1; i < m; i++) {
        for (int j = 1; j < n; j++) {
            dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1]);
        }
    }
    return dp[m-1][n-1];
}
復(fù)雜度以及優(yōu)化的點(diǎn)

復(fù)雜度
時(shí)間復(fù)雜度:O(mn)哩治,遍歷整個(gè)矩陣恰好一次秃踩。
空間復(fù)雜度:O(m
n),額外的一個(gè)同大小矩陣锚扎。
優(yōu)化的點(diǎn)
通過(guò)使用二維動(dòng)態(tài)規(guī)劃算法吞瞪,雖然可以解決這個(gè)問(wèn)題,但看起來(lái)時(shí)間復(fù)雜度以及空間復(fù)雜度和記憶搜索算法基本一樣驾孔,那是否有方法繼續(xù)優(yōu)化呢芍秆?

LeetCode提交結(jié)果:
二維dp算法提交結(jié)果.png

4. 一維動(dòng)態(tài)規(guī)劃算法

通過(guò)分析二維動(dòng)態(tài)規(guī)劃算法,發(fā)現(xiàn)當(dāng)更新完第一行和第一列以后翠勉,每次都是一行一行的對(duì)dp矩陣的其他位置進(jìn)行更新妖啥,那么可不可以只用一個(gè)數(shù)組就完成對(duì)其他位置的更新呢?答案是可以的对碌!可以將一維數(shù)組理解為從上往下滾動(dòng)的數(shù)組荆虱。因?yàn)槲覀儾⒉魂P(guān)心中間的狀態(tài)是什么,只需要將這個(gè)數(shù)組滾動(dòng)到最后朽们,計(jì)算最后的結(jié)果值即可怀读。本題分析如下圖所示:

一維動(dòng)態(tài)規(guī)劃.png

從圖中可以看到,一共滾動(dòng)了三次骑脱,第一次滾動(dòng)初始化dp數(shù)組菜枷,接下來(lái)每次借助二維數(shù)組和dp數(shù)組中的值對(duì)數(shù)組進(jìn)行滾動(dòng)更新,直到更新到最后一行(也就是本題的第三行)叁丧,最后一個(gè)標(biāo)紅的位置就是我們所求的結(jié)果啤誊。

實(shí)現(xiàn)代碼如下:

int DPAlgorithm::minPathSum(vector<vector<int>> &grid) {
    //一維dp算法
    size_t m = grid.size();  //行
    size_t n = grid[0].size(); //列
    vector<int> dp(n,-1);
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            
            if (i == 0 && j == 0) {
                //更新第一個(gè)元素
                dp[j] = grid[i][j];
            } else if (i == 0 && j > 0) {
                //更新第一行
                dp[j] = grid[i][j] + dp[j-1];
            } else if (j == 0 && i > 0) {
                //更新第一列
                dp[j] = grid[i][j] + dp[j];
            } else {
                //更新其他位置
                dp[j] = grid[i][j] + min(dp[j-1], dp[j]);
            }
        }
    }
    return dp[n-1];
}
復(fù)雜度以及優(yōu)化的點(diǎn)

復(fù)雜度
時(shí)間復(fù)雜度 :O(mn),遍歷整個(gè)矩陣恰好一次岳瞭。
空間復(fù)雜度 :O(n),額外的一維數(shù)組蚊锹,和一行大小相同

還能繼續(xù)優(yōu)化嗎瞳筏?

答案是可以的,可以將一維dp算法的思路應(yīng)用到二維dp算法上面牡昆。什么意思姚炕?因?yàn)槲覀冊(cè)谟?jì)算的過(guò)程中并不關(guān)心中間狀態(tài),而只關(guān)系最后一行迁杨,所以钻心,我們可以免去申請(qǐng)二維dp矩陣,直接在給定的矩陣上進(jìn)行滾動(dòng)铅协,這樣捷沸,空間復(fù)雜度可以降為O(1)。但這樣會(huì)改變?cè)仃囍械闹岛罚枰鶕?jù)實(shí)際情況來(lái)使用痒给,本題是可以使用的

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末骏全,一起剝皮案震驚了整個(gè)濱河市苍柏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌姜贡,老刑警劉巖试吁,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異楼咳,居然都是意外死亡熄捍,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)母怜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)余耽,“玉大人,你說(shuō)我怎么就攤上這事苹熏〉郑” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵轨域,是天一觀的道長(zhǎng)袱耽。 經(jīng)常有香客問(wèn)我,道長(zhǎng)干发,這世上最難降的妖魔是什么朱巨? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮铐然,結(jié)果婚禮上蔬崩,老公的妹妹穿的比我還像新娘。我一直安慰自己搀暑,他們只是感情好沥阳,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著自点,像睡著了一般桐罕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上桂敛,一...
    開(kāi)封第一講書(shū)人閱讀 48,970評(píng)論 1 284
  • 那天功炮,我揣著相機(jī)與錄音,去河邊找鬼术唬。 笑死薪伏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的粗仓。 我是一名探鬼主播嫁怀,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼借浊!你這毒婦竟也來(lái)了塘淑?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蚂斤,失蹤者是張志新(化名)和其女友劉穎存捺,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體曙蒸,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡捌治,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了逸爵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片具滴。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖师倔,靈堂內(nèi)的尸體忽然破棺而出构韵,到底是詐尸還是另有隱情,我是刑警寧澤趋艘,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布疲恢,位于F島的核電站,受9級(jí)特大地震影響瓷胧,放射性物質(zhì)發(fā)生泄漏显拳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一搓萧、第九天 我趴在偏房一處隱蔽的房頂上張望杂数。 院中可真熱鬧宛畦,春花似錦、人聲如沸揍移。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)那伐。三九已至踏施,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間罕邀,已是汗流浹背畅形。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留诉探,地道東北人日熬。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像肾胯,于是被迫代替她去往敵國(guó)和親碍遍。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345