博客 / 詳情

返回

劍指offer-72、禮物的最⼤價值

題⽬描述

在⼀個m × n的棋盤的每⼀格都放有⼀個禮物,每個禮物都有⼀定的價值(價值⼤於 0)。你可以從棋盤的左上⻆開始拿格⼦⾥的禮物,並每次向右或者向下移動⼀格、直到到達棋盤的右下⻆。給定⼀個棋盤及其上⾯的禮物的價值,請計算你最多能拿到多少價值的禮物?

如輸⼊這樣的⼀個⼆維數組,

[
[1,3,1],
[1,5,1],
[4,2,1]
]

那麼路徑 1→3→5→2→1 可以拿到最多價值的禮物,價值為 12

思路及解答

基礎動態規劃

這道題其實⼀看就知道是動態規劃,棋盤中的每個⼩格⼦,都是和上⽅,或者左⽅的格⼦有關。既然是動態規劃,那麼我們先定義狀態:

dp[i][j]表示到達(i,j)位置時能獲得的最大禮物價值

狀態轉移:dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + grid[i][j]

public int maxValue(int[][] grid) {
    if (grid == null || grid.length == 0 || grid[0].length == 0) {
        return 0;
    }
    
    int m = grid.length, n = grid[0].length;
    int[][] dp = new int[m][n];
    
    // 初始化起點
    dp[0][0] = grid[0][0];
    
    // 初始化第一行:只能從左邊來
    for (int j = 1; j < n; j++) {
        dp[0][j] = dp[0][j-1] + grid[0][j];
    }
    
    // 初始化第一列:只能從上邊來
    for (int i = 1; i < m; i++) {
        dp[i][0] = dp[i-1][0] + grid[i][0];
    }
    
    // 填充其餘位置
    for (int i = 1; i < m; i++) {
        for (int j = 1; j < n; j++) {
            dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]) + grid[i][j];
        }
    }
    
    return dp[m-1][n-1];
}

每個位置的計算只依賴左邊和上邊的結果,通過雙重循環自左上向右下填充整個dp表

  • 時間複雜度:O(mn)
  • 空間複雜度:O(mn)

空間優化動態規劃

觀察發現當前行只依賴上一行,可以使用一維數組進行空間優化,利用dp[j]在更新前存儲上一行第j列的值,更新後存儲當前行第j列的值,實現空間複用

dp[j]表示當前行第j列的最大價值,滾動更新

public int maxValue(int[][] grid) {
    if (grid == null || grid.length == 0 || grid[0].length == 0) {
        return 0;
    }
    
    int m = grid.length, n = grid[0].length;
    int[] dp = new int[n];
    
    // 初始化第一行
    dp[0] = grid[0][0];
    for (int j = 1; j < n; j++) {
        dp[j] = dp[j-1] + grid[0][j];
    }
    
    // 處理後續行
    for (int i = 1; i < m; i++) {
        // 更新第一列
        dp[0] += grid[i][0];
        
        for (int j = 1; j < n; j++) {
            // dp[j]代表上一行第j列的值(從上方來)
            // dp[j-1]代表當前行第j-1列的值(從左邊來)
            dp[j] = Math.max(dp[j], dp[j-1]) + grid[i][j];
        }
    }
    
    return dp[n-1];
}
  • 時間複雜度:O(mn)
  • 空間複雜度:O(n)

原地修改動態規劃(最優解)

修改原數組,直接使用grid數組作為dp表,避免額外空間分配

public int maxValue(int[][] grid) {
    if (grid == null || grid.length == 0 || grid[0].length == 0) {
        return 0;
    }
    
    int m = grid.length, n = grid[0].length;
    
    // 初始化第一行
    for (int j = 1; j < n; j++) {
        grid[0][j] += grid[0][j-1];
    }
    
    // 初始化第一列
    for (int i = 1; i < m; i++) {
        grid[i][0] += grid[i-1][0];
    }
    
    // 填充其餘位置
    for (int i = 1; i < m; i++) {
        for (int j = 1; j < n; j++) {
            grid[i][j] += Math.max(grid[i-1][j], grid[i][j-1]);
        }
    }
    
    return grid[m-1][n-1];
}
  • 時間複雜度: O(nm) ,需要計算完⾥⾯的⼩格⼦
  • 空間複雜度: O(1) ,優化後可以實現原地操作,不需要額外的空間
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.