數字塔問題(遞歸,遞推和記憶化搜索到動態規劃)

來自劉汝佳的《算法競賽入門經典(第二版)》,下面實現代碼均爲Java

動態規劃初步

數字三角形問題(數字塔):有一個非負整數組成的三角形,第一行只有一個數,除了最下行之外的每個數的左下方和右下方各有一個數。如下圖所示:計算從頂至底的路徑,使得總和最大。
數字塔問題
解題思路:
定義狀態d(i, j)爲從(i, j)出發時能得到的最大和,從(i, j)出發有兩種決策,往左或者往右。
  要求從(i, j)出發走到底部的最大值d(i, j),則相當於選擇從左下走或者從右下走中的較大值,即d(i+1, j)和d(i+1, j+1)的較大值,然後加上a(i, j),可得狀態轉移方程 d(i, j) = a(i, j) + max(d(i+1, j), d(i+1, j+1))。接下來是計算狀態轉移的不同代碼實現(下面i,j均從0開始)

1. 直接遞歸

自頂向下: 如果原來的三角形有n層,則調用關係樹也會有n層,一共有2^n - 1個子問題。子問題重複計算,因此時間效率不高。

/**
    * 遞歸:沒有考慮子問題的重複計算
    * 注意邊界
    * @param a
    * @param i
    * @param j
    * @return
    */
   int solver1(int a[][], int i, int j){
       int n = a.length;
       return i == n ? 0 : a[i][j] + Math.max(solver1(a, i+1, j), solver1(a, i+1, j+1));
   }

2. 遞推計算

自底向上d(i+1, j)和d(i+1, j+1)需要在d(i, j)之前完成計算。遞推完成後,直接返回輔助數組b[0][0]即可。
時間複雜度爲n^2

    /**
    * 遞推:自底向上
    * 注意邊界
    * @param a
    * @return
    */
   int solver2(int a[][]){
   	   //構造輔助數組b,避免修改原數組a
       int n = a.length;
       int[][] b = new int[n][a[n-1].length];
       for(int j = 0;j<a[n-1].length;j++){
           b[n-1][j] = a[n-1][j];
       }
       
       for(int i = a.length-2;i>=0;i--){
           for(int j = 0;j<a[i].length;j++){
               b[i][j] = a[i][j] + Math.max(b[i+1][j], b[i+1][j+1]);
           }
       }
       return b[0][0];
   }

3. 記憶化搜索

自底向上:時間複雜度爲n^2。構造記憶化數組,用於記錄已經計算過的子問題。

    /**
   * 記憶化搜索:暴力遞歸的改進版,仍然是遞歸,不過保存了計算的中間結果,避免計算重複子問題
   * @param a
   * @return
   */
  int solver3(int a[][], int dp[][], int i, int j){
      if(dp[i][j] > 0){
          return dp[i][j];
      }
      /* 返回賦值語句,否則就等同與第一種方法,子問題重複計算了 */
      return dp[i][j] = a[i][j] + Math.max(solver3(a,dp,i+1,j), solver3(a,dp,i+1,j+1));
  }

主函數輸出

  需要注意的是第三種記憶化搜索,記憶化數組用來保存已經計算過的狀態,因此記憶化數組初始化(Java默認初始化爲0)時,-1代表該狀態沒有被計算。

void solution(){
        int[][]a = {{1},{3, 2},{4, 10 ,1},{4, 3, 2, 20}};
        System.out.println("遞歸:"+solver1(a, 0, 0));
        System.out.println("遞推:"+solver2(a));

        int n = a.length;
        /* 初始化記憶矩陣 */
        int[][] dp = new int[n][a[n-1].length];
        for(int i = 0;i<dp.length;i++){
            for(int j = 0;j<dp[0].length;j++){
                dp[i][j] = -1;
            }
        }
        for(int j = 0;j<a[n-1].length;j++){
            dp[n-1][j] = a[n-1][j];
        }
        System.out.println("記憶化搜索:" + solver3(a, dp, 0, 0));
    }

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章