經典dp--搶家奪舍系列之GO寫法

漓與發表於2020-09-29

搶家奪舍系列–經典dp系列GO寫法

house-robber

首先我們從比較簡單的角度來做:

  1. 判斷狀態:題目只有兩種狀態,也就是偷或者不偷,所以我們可以建立一個二維的陣列,並用0表示不偷,1表示偷
  2. 陣列的長度:這個實際上就是你房子的數量;
  3. 狀態轉移方程:當你在第i家時,選擇不偷時,dp[i][0]就是選擇dp[i-1]中最大的一個,選擇偷的話,前面那家肯定不能偷,因此dp[i][1] = nums[i] + dp[i-1][0]。所以有狀態轉移方程:
dp[i][0] = Max(dp[i - 1][0], dp[i - 1][1])
dp[i][1] = = nums[i] + dp[i-1][0]
  1. 最後Max(dp[len - 1][0], dp[len - 1][1])就是結果啦
// accept code 程式碼一
func rob(nums []int) int {
    length := len(nums)
    if length < 1 {
        return 0
    }
    dp := make([][2]int, length)
    dp[0][1] = nums[0]
    dp[0][0] = 0
    for i := 1; i < length; i++ {
        dp[i][0] = max(dp[i-1][0], dp[i-1][1])
        dp[i][1] = dp[i-1][0] + nums[i]
    }
    return max(dp[length-1][0], dp[length-1][1])
}

func max(values ...int) int {
    maxValue := math.MinInt32
    for _, value := range values {
        if value > maxValue {
            maxValue = value;
        }
    }
    return maxValue;
}

值得注意的是,這個程式碼可以簡化成一維陣列,dp[i]表示當前i房子後能偷到的最多錢,所以我們可以利用i i-1 and i-2 來表示鄰避關係,也就是說有狀態轉移方程:

dp[i] = max(dp[i-2]+nums[i], dp[i-1])

//程式碼二
func rob(nums []int) int {
    length := len(nums)
    if length < 1 {
        return 0
    }
    if length == 1 {
        return nums[0];
    }
    
    dp := make([]int, length)
    dp[0] = nums[0]
    dp[1] = nums[1]
    for i := 2; i < length; i++ {
        dp[i] = max(dp[i-2]+nums[i], dp[i-1])
    }
    return dp[length-1]
}

func max(values ...int) int {
    maxValue := math.MinInt32
    for _, value := range values {
        if value > maxValue {
            maxValue = value;
        }
    }
    return maxValue;
}

通過程式碼二我們可以進一步優化空間複雜度,因為我們可見不論怎樣,dp狀態轉移方程只跟
i i-1 and i-2 這三者有關,所以我們可以只使用三個變數dp, dp1, dp2來進行更新:

dp = max(dp1 + nums[i], dp2)
dp1 = dp2
dp2 = dp

因此有程式碼:

//程式碼三
func rob(nums []int) int {
    length := len(nums)
    if length < 1 {
        return 0
    }
    
    dp1 := 0 //初始化為0 邊界需要判斷清晰
    dp2 := 0
    dp := 0
    for i := 0; i < length; i++ {
        dp = max(dp1 + nums[i], dp2)
        dp1 = dp2
        dp2 = dp
    }
    return dp
}

func max(values ...int) int {
    maxValue := math.MinInt32
    for _, value := range values {
        if value > maxValue {
            maxValue = value;
        }
    }
    return maxValue;
}

house-robber-II

這個題目有限制就是陣列是為一個環形陣列,因此我們可以分為兩種情況考慮,當選第一個來偷時,就不再考慮最後一個房子;當選最後一個來偷時,就不再考慮第一個房子;最後對於這兩種情況選出一個最大值出來就好啦。
所以改改上面的程式碼就好了:

func rob(nums []int) int {
    length := len(nums)
    if length < 1 {
        return 0;
    } else if length == 1 {
        return nums[0];
    }
    return max(subRob(nums,0, length - 2), subRob(nums, 1, length - 1))
}

func subRob(nums []int, st, ed int) int {
    dp := 0
    dp1 := 0
    dp2 := 0
    
    for i := st; i <= ed; i++ {
        dp = max(dp1 + nums[i], dp2)
        dp1 = dp2
        dp2 = dp
    }
    
    return dp
}

func max(a, b int) int {
    if a < b {
        return b
    }
    return a
}

house-robber-III

這個是採用樹的形式,同理,對於某一個節點我們可偷可不偷,我們儲存兩個狀態,rob and noRob。 所以有計算公式:

rob = val + 左節點不偷的錢 + 右節點不偷的錢
noRob = 左節點所能偷到錢最大值 + 右節點所能偷到錢最大值

func rob(root *TreeNode) int {
    if root == nil {
        return 0
    }   
    noRob, rob := getResult(root);
    return Max(noRob, rob);
}

func getResult(root *TreeNode) (a, b int) {
    if root == nil{
        return 0, 0
    }   
    left0, left1 := getResult(root.Left)
    right0, right1 := getResult(root.Right)
    
    rob := root.Val + left0 + right0
    // 可偷子節點或者不偷子節點,選最大的那個情況就好
    noRob := Max(left0, left1) + Max(right0, right1)
    
    return noRob, rob
}

func getResult(root *TreeNode) (noRob, rob int) {
    if root == nil{
        return 0, 0
    }   
    left0, left1 := getResult(root.Left)
    right0, right1 := getResult(root.Right)
    
    rob = root.Val + left0 + right0
    noRob = Max(left0, left1) + Max(right0, right1)
    
    return
}


func Max(a,b int) int {
    if a < b {
        return b
    } else {
        return a
    }
}

相關文章