2019.10.18 #程序员笔试必备# LeetCode 从零单刷个人笔记整理(持续更新)
github:https://github.com/ChopinXBP/LeetCode-Babel
可以回顾一下初级版的LeetCode198:打家劫舍,这是一道一维dp,状态转移方程也很简单:
dp[i] = Math.max(dp[i - 2] + nums[cur], dp[i - 1]);
当前偷窃的最大金额可能有两种来源情况,要么偷了这家,上一家没偷;要么偷了上一家,这家没偷。因此每一个结点的dp值和前两个结点的dp值有关。
这道升级版的树状动态规划问题也可以由一维的dp延伸来,只是情况稍微复杂一些,每一个节点的dp值与三层二叉树的结点dp值相关。对于下图所示的一棵三层满二叉树来说:
1
/ \
2 3
/ \ / \
4 5 6 7
在每个结点的金额非负的情况下,且要保证取值结点不相邻,只可能有四种最大的取值方式:
1. 结点2 + 结点3
2. 结点1 + 结点4 + 结点5 + 结点6 + 结点7
3. 结点2 + 结点6 + 结点7
4. 结点3 + 结点4 + 结点5
那么我们可以自底向上递归进行这个dp运算,令dp[i]代表以i结点为根节点的子树的最大偷窃金额值,计算结束后将dp值直接保存在i结点的val值当中返回。可以推出状态转移方程为:
dp[root] = Max(dp[l]+dp[r], root.val+dp[ll]+dp[lr]+dp[rr]+dp[rl], dp[l]+dp[rl]+dp[rr], dp[r]+dp[lr]+dp[rl]);
分别对应上述四种情况。而观察发现,在dp[l]和dp[r]的计算中实际已经包含了dp[ll]、dp[lr]、dp[rr]、dp[rl]的取舍情况,因此可以简化为前两种情况。状态转移方程简化为:
dp[root] = Max(dp[l]+dp[r], root.val+dp[ll]+dp[lr]+dp[rr]+dp[rl]);
为了方便运算,我们一般会为dp数组赋予初值。在树状dp中同样,我们需要将每一个非叶结点作为根节点的子树构造成一棵三层满二叉树方便运算。
对于叶子结点,我们给其添加值为0的左右子结点。
对于左/右子树为空的非叶节点,我们在其左/右添加一棵两层值为0的满二叉树。
传送门:打家劫舍
The thief has found himself a new place for his thievery again. There is only one entrance to this area, called the “root.” Besides the root, each house has one and only one parent house. After a tour, the smart thief realized that “all houses in this place forms a binary tree”. It will automatically contact the police if two directly-linked houses were broken into on the same night.
Determine the maximum amount of money the thief can rob tonight without alerting the police.
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
示例 1:
输入: [3,2,3,null,3,null,1]
3
/ \
2 3
\ \
3 1
输出: 7
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.
示例 2:
输入: [3,4,5,1,3,null,1]
3
/ \
4 5
/ \ \
1 3 1
输出: 9
解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.
/**
*
* The thief has found himself a new place for his thievery again. There is only one entrance to this area, called the "root."
* Besides the root, each house has one and only one parent house. After a tour, the smart thief realized that "all houses in this place forms a binary tree".
* It will automatically contact the police if two directly-linked houses were broken into on the same night.
* Determine the maximum amount of money the thief can rob tonight without alerting the police.
* 在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。
* 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。
* 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
* 计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
*
*/
public class HouseRobberIII {
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}
public int rob(TreeNode root) {
return Solution(root).val;
}
//树形动态规划:自底向上
//参考LeetCode198打家劫舍I的一维dp思路:dp[i] = Math.max(dp[i-2]+nums[cur], dp[i-1]),每一个节点的dp值与三层二叉树的结点dp值相关。
public TreeNode Solution(TreeNode root){
//设定初值:在叶子结点和空结点下进行构建,使得每一个非叶结点作为根节点的子树都是一棵三层满二叉树,方便动态转移方程运算。
//对于左/右子树为空的非叶节点,我们在其左/右添加一棵两层值为0的满二叉树。
//对于叶子结点,我们给其添加值为0的左右子结点。
if(root == null){
TreeNode newNode = new TreeNode(0);
return Solution(newNode);
}
if(root.left == null && root.right == null){
root.left = new TreeNode(0);
root.right = new TreeNode(0);
return root;
}
root.left = Solution(root.left);
root.right = Solution(root.right);
//那么我们可以自底向上递归进行这个dp运算,令dp[i]代表以i结点为根节点的子树的最大偷窃金额值,计算结束后将dp值直接保存在i结点的val值当中返回。
//在每个结点的金额非负的情况下,且要保证取值结点不相邻,三层满二叉树的最大取值只可能有四种情况。
//状态转移方程为dp[root] = Max(dp[l]+dp[r], root.val+dp[ll]+dp[lr]+dp[rr]+dp[rl], dp[l]+dp[rl]+dp[rr], dp[r]+dp[lr]+dp[rl])
//在dp[l]和dp[r]的计算中实际已经包含了dp[ll]、dp[lr]、dp[rr]、dp[rl]的取舍情况,因此可以简化为前两种情况。
//状态转移方程为dp[root] = Max(dp[l]+dp[r], root.val+dp[ll]+dp[lr]+dp[rr]+dp[rl])
root.val = Math.max(root.left.val + root.right.val, root.val + root.left.left.val + root.left.right.val + root.right.left.val + root.right.right.val);
return root;
}
}
#Coding一小时,Copying一秒钟。留个言点个赞呗,谢谢你#