题目描述
这是力扣上的 337. 打家劫舍 III,难度为 中等。
题目分析
做了打家劫舍的前两个题,多多少少摸到一点门道了吧,无论房子是一排,还是环形的房屋,或者是当前本题的二叉树型的房屋,咱们的核心思想还是对于每一个房间,咱们是 选,还是不选,也就是又回到了这么一个哲学问题,偷还是不偷
那么对于二叉树的房型,我们仍然还是考虑,每一个房间偷还是不偷,例如我们令当前的节点为 n,n 的左子树是 l,右子树是 r,如果偷则令函数为 T,不偷则令函数为NT,那么就会有如下的考虑:
思考流程
如果偷当前的节点 n,那么左子和右子节点就不能偷了,那么就可以有这样的公式
T(n)=node.Val+NT(l)+NT(r)T(n) = node.Val + NT(l) + NT(r)T(n)=node.Val+NT(l)+NT(r)
另外,第二种情况,如果当前的 n 不偷,那么当前的左子和右子是可以偷的,但是也可以不偷,这个就看实际偷和不偷的价值了,咱们取最大的就可以了
那么就有这样的递推公式:
NT(n)=max(T(l),NT(l))+max(T(r),NT(r))NT(n) = max(T(l), NT(l)) + max(T(r), NT(r))NT(n)=max(T(l),NT(l))+max(T(r),NT(r))
那么,如果这个 n 是 root 节点的话,对于 root 根节点的话也是一样的效果,咱们可以选择偷或者不偷,那么就有这个结果
max(T(root),NT(root))max(T(root), NT(root))max(T(root),NT(root))
最后,我们知道了递推公式,但是我们需要如何去处理数据的问题,根据递推公式,我们可以知道,root 节点的偷还是不偷的现金最大值,依赖于他的子树偷还是不偷,那么咱们可以使用后序遍历的方式来进行处理,即遍历左右根的方式来处理即可
那么对于每一个节点的偷还是不偷的值,我们如何来记录呢?
仔细思考,每一个节点的最大现金只是依赖他的子节点偷还是不偷,那么咱们只需要给每一个节点维护一个临时的数组即可,数组中第一个元素是偷当前节点的最大现金,第二个选择是不偷当前节点的最大现金
接下来,咱们就可以来实现编码了,咱们这次使用 golang 的方式来进行实现
Golang 的实现方式:
- 实现每一个节点去处理选他还是不选他,如果选他,那么他的子节点就不能选,如果不选他,那么他的子节点可选可不选
- 我们使用一个临时的数组来表示一个节点的选择和不选的最大金额情况,如 []int{选择当前节点的最大金额,不选择当前节点的最大金额}
/** * Definition for a binary tree node. * type TreeNode struct { * Val int * Left *TreeNode * Right *TreeNode * } */ func rob(root *TreeNode) int { res := dfs(root) // 比较选还是不选根节点,哪个现金多就选哪个 return max(res[0], res[1]) } func dfs (node *TreeNode)[]int{ if node == nil { return []int{0, 0} } left,right := dfs(node.Left), dfs(node.Right) // 选择当前节点,不选择左子和右子 sel := node.Val + left[1] + right[1] // 不选择当前节点,那么咱们就看子节点是否需要选择,咱们还比较一下子节点的数据 选或者不选,谁更大 nosel := max(left[0], left[1]) + max(right[0], right[1]) return []int{sel, nosel} } func max(a,b int) int{ if a>b { return a } return b }
这种实现方式,咱们的时间复杂度 O(n),即是咱们深度优先遍历二叉树的时间,空间复杂度也是 O(n),咱们深度遍历是需要消耗占空间的
今天就到这里,若有偏差,还请斧正
欢迎点赞,关注,收藏
朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力
好了,本次就到这里
技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。
我是阿兵云原生,欢迎点赞关注收藏,下次见~