領扣-120 三角形最小路徑和 Triangle MD


三角形最小路徑和 Triangle

數組 動態規划

問題

給定一個三角形,找出自頂向下的最小路徑和。每一步只能移動到下一行中相鄰的結點上。

例如,給定三角形:

     [2],
    [3,4],
   [6,5,7],
  [4,1,8,3]

自頂向下的最小路徑和為 11(即,2 + 3 + 5 + 1 = 11)。

方法聲明:

class Solution {
    public int minimumTotal(List<List<Integer>> triangle) {

    }
}

 

動態規划(基礎)

分析

如果只是簡單分析的話,這道題和其他動態規划沒啥區別,問題是代碼寫起來可能稍有點麻煩。

用動態規划分析的關鍵是發現遞推式,我們定義一個二維數組 dp,其中 dp[i][j] 的含義為:

dp[i][j]:到達三角形第 i 行 第 j 那個元素所需要的最小步數

那么我們就可以分析出 dp[i][j] 的值的規律,所以很明顯,遞推式是這樣的:

dp[i][j] = V(i,j) + min(dp[i-1][j-1] ,dp[i-1][j])

當然,針對邊界元素我們需要單獨處理一下,具體的代碼實現如下:

代碼

class Solution {

    public int minimumTotal(List<List<Integer>> triangle) {
        int len = triangle.size();
        int[][] total = new int[len][len]; //保存的是到達最后一行各個元素的最短距離
        total[0][0] = triangle.get(0).get(0);

        for (int i = 1; i < len; i++) {
            for (int j = 0; j <= i; j++) {
                if (j == 0) {
                    total[i][j] = triangle.get(i).get(j) + total[i - 1][j];
                } else if (j == i) {
                    total[i][j] = triangle.get(i).get(j) + total[i - 1][j - 1];
                } else {
                    total[i][j] = triangle.get(i).get(j) + Math.min(total[i - 1][j], total[i - 1][j - 1]);
                }
            }
        }

        int min = Integer.MAX_VALUE;
        for (int i = 0; i < len; i++) {
            min = Math.min(min, total[len - 1][i]);
        }
        System.out.println(Arrays.toString(total[len - 1]));

        return min;
    }
}

 

時間復雜度 O(n^2)
空間復雜度 O(n^2)

動態規划(逆向)

分析

假如我們是從倒數第二行(統一稱為第 i 行)開始走的話,那么從倒數第二行的第 j 個元素走到最后一行所需要的最小步數為:

P(j) = V(i,j) + min(V(i+1,j),V(i+1,j+1))

我們對倒數第二行的所有元素都進行此計算,並將結果保存到集合 array 中,且 array 的第 j 個元素的值為 P(j)。

我們知道 P(j) 代表的含義為倒數第二行的第 j 個元素走到最后一行所需要的最小步數,所以如果讓我們計算從倒數第二行走到最后一行所需要的最小步數,那么我們只需從 array 中選擇值最小的那個元素即可。

同樣道理,如果讓我們從倒數第三行開始走的話,那么我們只需按同樣的方式計算從倒數第三行的第 j 個元素走到 array 列 所需要的最小步數即可,而不需要關注倒數第二行和倒數第三行的數了。

經過此步驟后,我們發現問題的規模變小了,動態規划思想也就體現出來了。


遍歷過程:

     [2],
    [3,4],
   [6,5,7],
  [4,1,8,3]

     [2],
    [3,4],
   [7,6,10],

     [2],
    [9,10],

     [11]

代碼

class Solution {

    public int minimumTotal(List<List<Integer>> triangle) {
        int[][] dp = new int[triangle.size() + 1][triangle.size() + 1];// 加1可以不用初始化最后一層
        for (int i = triangle.size() - 1; i >= 0; i--) {
            List<Integer> curTr = triangle.get(i);
            for (int j = 0; j < curTr.size(); j++) {
                dp[i][j] = Math.min(dp[i + 1][j], dp[i + 1][j + 1]) + curTr.get(j);
            }
        }
        return dp[0][0];
    }
}

 

時間復雜度 O(n^2)
空間復雜度 O(n^2)

動態規划(逆向 + 優化)

對於上面那種方式,我們可以繼續優化。

我們發現在計算倒數第三行時,array 的長度和倒數第二行的長度相同,且有 替換 倒數第二行的效果,所以,我們可以不必再定義一個 array ,而只需把計算的結果賦給倒數第二行即可。

而在計算過程中,我們需要的 array 的長度會越來越小,這沒關系,我們只需要關心自己需要的那些元素即可。

class Solution {

    public int minimumTotal(List<List<Integer>> triangle) {
        int[] dp = new int[triangle.size() + 1]; // 加1可以不用初始化最后一層

        for (int i = triangle.size() - 1; i >= 0; i--) {
            List<Integer> curTr = triangle.get(i);
            for (int j = 0; j < curTr.size(); j++) {
                dp[j] = curTr.get(j) + Math.min(dp[j], dp[j + 1]); //這里的dp[j] 使用的時候默認是上一層的,賦值之后變成當前層
            }
        }
        return dp[0];
    }
}

 

時間復雜度 O(n^2)
空間復雜度 O(n)

實際測試會發現,雖然空間復雜度大大降低了,但是總耗時卻增加了。這也很好解釋,因為時間復雜度並沒有變,而空間復雜度減小又是在建立在增加了計算的基礎上的,所以總時間當然會增加了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM