Leetcode-打家劫舍

Leetcode打家劫舍系列题

打家劫舍是动态规划的经典题,虽然可以用其他方法解,但是在本文主要介绍动态规划的解法。

198.打家劫舍

题目描述
在这里插入图片描述
分析

动态规划在于找转移方程

  1. 找出口
    1. 如果只有一家,直接偷就完事了
    2. 如果有两家,看两家哪家偷得多
  2. 转移方程
    1. 如果n=3, 那么两种情况,偷第二家和偷第一,第三家
      nums[3] += nums[1]
    2. n>3时,从第四家开始,前一家有可能来源于前面的第二家,或者第三家,取决于哪家偷得多。
  3. 遍历结束,判断倒数第一家和第二家哪家偷的多,返回

代码

func rob(nums []int) int {
	if len(nums) == 1 {
		return nums[0]
	}
	if len(nums) == 2 {
		return max(nums[0], nums[1])
	}
	nums[2] += nums[0]
	// 从i>2家开始,偷的上一家可能是i-2 或者 i-3 取决于之前哪家偷的多
	for i := 3; i < len(nums); i++ {
		nums[i] += max(nums[i-2], nums[i-3])
	}
	// 最后有可能偷最后一家多,也有可能偷倒数第二家多
	return max(nums[len(nums)-1], nums[len(nums)-2])
}

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

213.打家劫舍Ⅱ

题目描述
在这里插入图片描述
分析

相较于第一种方式,第二种最后一家和第一家认为是相邻的,也就是不能同时偷这两家。
方法:
分组动态规划,对第 1 家到第 n-1家动态规划,对第二家到第n家动态规划,最后比较两组动态规划的最优解

代码

func rob(nums []int) int {
	dp1 := make([]int, len(nums)-1)
	dp2 := make([]int, len(nums)-1)
	if len(nums) == 1 {
		return nums[0]
	}
	dp1[0] = nums[0]
	if len(nums) == 2 {
		return max(nums[0], nums[1])
	}
	dp1[1] = nums[1]
	dp2[0] = nums[1]
	if len(nums) == 3 {
		return max(max(nums[0], nums[1]), nums[2])
	}
	dp1[2] = nums[2] + dp1[0]
	dp2[1] = nums[2]
	if len(nums) == 4 {
		return max(nums[0] + nums[2], nums[1]+nums[3])
	}
	dp2[2] = nums[3] + dp2[0]
	for i := 3; i < len(nums)-1; i++ {
		dp1[i] = max(dp1[i-2], dp1[i-3]) + nums[i]
		dp2[i] = max(dp2[i-2], dp2[i-3]) + nums[i+1]
	}
	// retDP1 方案一的偷盗结果
	// retDP2 方案二的偷盗结果
	retDP1 := max(dp1[len(dp1)-1], dp1[len(dp1)-2])
	retDP2 := max(dp2[len(dp2)-1], dp2[len(dp2)-2])
	return max(retDP1, retDP2)
}

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

337.打家劫舍Ⅲ

题目描述
在这里插入图片描述

tips

树形结构,涉及到数据的遍历,所以这题的解法是基于DFS的。
不过我个人觉得,动态规划并非一定要有一个dp存储状态转移量,只要存在状态转移的,都可以认为是动态规划的解法。
所以我觉得本题解法可以称为基于DFS的动态规划

分析

  1. 题意理解
    这一题是打家劫舍系列第三题,这一题需要偷的房子是树形的,报警的条件同样是偷了相邻的则报警。只不过这里的房子在树上,求解小偷在不触发报警的情况下怎么偷得最多的钱。
  2. 基于DFS搜索(左右根),先搜索孩子节点,再判断当前节点。
  3. 确立状态
    1. 当前节点有两种状态,被偷(rb),或者不被偷(nrb)
    2. 如果被偷
      • 因为与孩子节点相邻,所以孩子节点不能被偷
      • currb = cur.value + L.nrb + R.nrb
    3. 如果不被偷
      • 那么当前节点的状态来源于孩子节点被偷或者不被偷
      • curnrb = max(L.rb, L.nrb) + max(R.rb, R.nrb)

代码

func rob(root *TreeNode) int {
	// 自定义比较函数 a和b谁大返回谁
	max := func(a, b int) int {
		if a < b {
			return b
		}
		return a
	}
	// 定义搜索算法
	var dfs func(root *TreeNode) (beRobbed, beNotRobbed int)
	dfs = func(root *TreeNode) (beRobbed, beNotRobbed int) {
		if root == nil {
			return 0, 0
		}
		// 搜索左子树,lbr表示左孩子节点被打劫, lnbr表示左孩子节点未被打劫
		lbr, lnbr := dfs(root.Left)
		// 搜索右子树,rbr表示右孩子节点被打劫,rnbr表示右孩子节点未被打劫
		rbr, rnbr := dfs(root.Right)
		// curbr表示当前节点被打劫
		// 当前节点被打劫的话,那么孩子节点就未被打劫
		curbr := root.Val + lnbr + rnbr
		// curnbr表示当前节点未被打劫
		// 当前节点未被打劫 那么孩子节点可能被打劫,也可能不被打劫,取决于哪种情况打劫收入高
		curnbr := max(lbr, lnbr) + max(rbr, rnbr)
		return curbr, curnbr
	}
	return max(dfs(root))
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值