一、打家劫舍
当前房屋偷与不偷,取决于前一个房屋和前两个房屋是否被偷
五部曲
dp[i],表示i以内的房屋,最多可以偷窃的金额- 确定递推公式,
dp[i] = Math.max(dp[i-2] + nums[i], dp[i-1]) - 确定初始值,
dp[0] = nums[0];dp[1] = Math.max(nums[0], nums[1]) - 确定遍历顺序,从前到后
- 举例推导
/**
* @param {number[]} nums
* @return {number}
*/
var rob = function(nums) {
let dp = new Array(nums.length).fill(0)
dp[0] = nums[0]
dp[1] = Math.max(nums[0], nums[1])
for(let i = 2; i < nums.length;i++) {
dp[i] = Math.max(dp[i-2] + nums[i], dp[i-1])
}
return dp[nums.length - 1]
};
二、打家劫舍2
和上题的区别在于,是环形
有两种场景
- 不考虑首元素下的最大总金额
- 不考虑尾元素下的最大总金额
结果,取这两种情况的最大值即可
/**
* @param {number[]} nums
* @return {number}
*/
var rob = function(nums) {
if(nums.length === 0) {
return 0
}
if(nums.length === 1) {
return nums[0]
}
let result1 = robRange(nums.slice(0, nums.length - 1))
let result2 = robRange(nums.slice(1, nums.length))
return Math.max(result1, result2)
};
var robRange = function(nums) {
let dp = new Array(nums.length).fill(0)
dp[0] = nums[0]
dp[1] = Math.max(nums[0], nums[1])
for(let i = 2; i < nums.length;i++) {
dp[i] = Math.max(dp[i-2] + nums[i], dp[i-1])
}
return dp[nums.length - 1]
};
三、打家劫舍3
暴力解法,后序遍历
超时,计算过程重复
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number}
*/
var rob = function (root) {
function dfs(node) {
if (!node) {
return 0
}
if (!node.left && !node.right) {
return node.val
}
let val1 = node.val
if (node.left) {
val1 += dfs(node.left.left) + dfs(node.left.right)
}
if (node.right) {
val1 += dfs(node.right.left) + dfs(node.right.right)
}
let val2 = dfs(node.left) + dfs(node.right)
let res =
return Math.max(val1, val2)
}
return dfs(root)
};
时间复杂度为O(n^2),空间复杂度为O(logn)
记忆搜索
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number}
*/
var rob = function (root) {
let map = new Map
function dfs(node) {
if (!node) {
return 0
}
if (!node.left && !node.right) {
return node.val
}
if(map.has(node)) {
return map.get(node)
}
let val1 = node.val
if (node.left) {
val1 += dfs(node.left.left) + dfs(node.left.right)
}
if (node.right) {
val1 += dfs(node.right.left) + dfs(node.right.right)
}
let val2 = dfs(node.left) + dfs(node.right)
let res = Math.max(val1, val2)
map.set(node, res)
return res
}
return dfs(root)
};
时间复杂度为On,空间复杂度为Ologn
动态规划
深度递归搜索对每个节点偷与不偷没有做记录,动态规划需要使用动态转移容器来记录状态的变化,可以使用长度为2的数组,记录当前节点偷或者不偷,得到的最大金钱
树形dp,基于递归三部曲+动态规划五部曲
- 确定递归函数的参数和返回值, dp数组为长度为2的数组,表示当前节点偷或者不偷,索引0,表示不偷,索引1表示偷,得到的最大金钱,递归的过程中,系统栈会保存每一层递归的参数
function robTree(node: number): [number, number] {
return []
}
- 确定终止条件,空节点,偷或不偷,都是0
if(!node) {
return [0, 0]
}
- 确定遍历顺序,后序遍历,需要通过递归遍历的返回值做下一步计算
- 确定单层递归的逻辑
- 如果偷当前节点,则左右孩子就不能偷,
val + left[0] + right[0] - 如果不偷当前节点,则左右孩子可以偷,得到两种情况的最大值,
val2 = Math.max(left[0], left[1]) + Math.max(right[0], right[1])
- 如果偷当前节点,则左右孩子就不能偷,
- 举例推导
var rob = function (root) {
function dfs(node) {
if(!node) {
return [0, 0]
}
let left = dfs(node.left)
let right = dfs(node.right)
// 不偷当前节点,取偷左孩子,或者不偷左孩子和偷右孩子或不偷右孩子的最大值之和
let val1 = Math.max(...left) + Math.max(...right)
// 偷当前节点,取左右孩子节点不偷的结果
let val2 = node.val + left[0] + right[0]
return [val1, val2]
}
return Math.max(...dfs(root))
};