经典dp--抢家夺舍系列之Go写法

经典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
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值