337. House Robber III
MediumDescription
房屋排列成二元樹形狀,每個節點有一定金額。不能同時搶劫直接相連的兩個房屋(父子節點)。回傳在不觸發警報的情況下能搶到的最大金額。
Example:
Input: root = [3,2,3,null,3,null,1] Output: 7 Explanation: 3 + 3 + 1 = 7
Intuition#
每個節點有兩個狀態:搶或不搶,用後序遍歷自底向上決策。
- 如果搶當前節點,就不能搶左右子節點
- 如果不搶當前節點,左右子節點可搶可不搶(取最大值)
- 每個節點回傳兩個值:搶/不搶的最大收益
Approaches#
1: 遞迴 + HashMap 記憶化#
- 概念: 對每個節點計算搶與不搶的最大值,用 HashMap 避免重複計算
- 時間複雜度:
O(n) - 空間複雜度:
O(n)
class Solution {
private val memo = HashMap<TreeNode, Int>()
fun rob(root: TreeNode?): Int {
if (root == null) return 0
memo[root]?.let { return it }
// 搶當前節點
var robCurrent = root.`val`
root.left?.let {
robCurrent += rob(it.left) + rob(it.right)
}
root.right?.let {
robCurrent += rob(it.left) + rob(it.right)
}
// 不搶當前節點
val skipCurrent = rob(root.left) + rob(root.right)
val result = maxOf(robCurrent, skipCurrent)
memo[root] = result
return result
}
}⭐ 2: 後序遍歷回傳 Pair(搶/不搶)#
- 概念: 每個節點回傳 (搶的最大值, 不搶的最大值),一次遍歷完成
- 時間複雜度:
O(n) - 空間複雜度:
O(h),h 為樹高
class Solution {
fun rob(root: TreeNode?): Int {
val (robIt, skipIt) = dfs(root)
return maxOf(robIt, skipIt)
}
// 回傳 Pair(搶此節點的最大值, 不搶此節點的最大值)
private fun dfs(node: TreeNode?): Pair<Int, Int> {
if (node == null) return 0 to 0
val (leftRob, leftSkip) = dfs(node.left)
val (rightRob, rightSkip) = dfs(node.right)
val robCurrent = node.`val` + leftSkip + rightSkip
val skipCurrent = maxOf(leftRob, leftSkip) + maxOf(rightRob, rightSkip)
return robCurrent to skipCurrent
}
}🔑 Takeaways#
- Pattern: 樹上 DP,每個節點維護多個狀態(搶/不搶),後序遍歷自底向上合併
- 關鍵技巧: 回傳 Pair 避免 HashMap 的開銷,且語義清晰;這是「樹形 DP」的經典模板