打家劫舍
[LeetCode198.House Robber]
今晚,你是一个抢劫犯,去抢劫一条街上的居民。抢劫两个相邻的用户你将会触发报警装置,怎么安排抢劫策略才能抢到最多的钱。
Example 1:
Input: [1,2,3,1]
Output: 4
Example 2:
Input: [2,7,9,3,1]
Output: 12
1. 动态规划:
dp[i]表示到第i家可以获利最多。(i从0开始,dp数组共有i+1个数)
假设nums为{3, 2, 1, 5},接下来我们推导dp数组的表示。
首先dp[0]=0,dp[1]=3没啥疑问,再看dp[2]是多少呢,由于3比2大,所以我们抢第一个房子的3,当前房子的2不抢,所以dp[2]=3,再来看dp[3],由于不能抢相邻的,所以我们可以用dp[i-2]的值加上当前的房间值nums[i-1](nums[2]即为第3个房间的值),和当前房间的前面一个dp值(dp[i-1])比较,取较大值当做当前dp值。
则我们写出递推公式:
dp[i] = max(dp[i-1], dp[i-2] + nums[i-1])
由此看出我们需要初始化dp[0]和dp[1],其中dp[0]为0, dp[1]为max(0, num[0])=num[0]。
Solution:
int rob(vector<int>& nums) {
int n = nums.size();
if(n == 0)
return 0;
vector<int> dp(n + 1 , 0);
dp[1] = nums[0];
for(int i = 2; i <= n; i ++)
dp[i] = max(dp[i - 1], dp[i - 2] + nums[i - 1]);
return dp[n];
}
2.单双计数
分别维护两个变量count_one和count_two,然后按照奇偶来分别更新他们,这样可以保证组成最大和的数字不相邻。
class Solution(object):
def rob(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
if not nums:
return 0
count_one = 0
count_two = 0
for i in range(len(nums)):
if i % 2 == 0:
count_two = max(count_two + nums[i], count_one)
else:
count_one = max(count_one + nums[i], count_two)
return max(count_one, count_two)
[LeetCode213.House_Robber_II]
现在房子排成了一个圆圈。如果抢了第一家,就不能抢最后一家,因为首尾相连了。
Example 1:
Input: [2,3,2]
Output: 3
Example 2:
Input: [1,2,3,1]
Output: 4
第一家和最后一家只能抢其中的一家,或者都不抢。我们这里变通一下,如果我们把第一家和最后一家分别去掉,各算一遍能抢的最大值,然后比较两个值取其中较大的一个即为所求。那我们只需参考之前的 House Robber 打家劫舍中的解题方法,然后调用两边取较大值。
class Solution:
def rob(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
length = len(nums)
if length == 0:
return 0
elif length == 1:
return nums[0]
else:
return max(self.rob_no_circle(nums[:-1]), self.rob_no_circle(nums[1:]))
def rob_no_circle(self, nums):
if not nums:
return 0
count_one = 0
count_two = 0
for i in range(len(nums)):
if i % 2 == 0:
count_two = max(count_two + nums[i], count_one)
else:
count_one = max(count_one + nums[i], count_two)
return max(count_one, count_two)
[LeetCode337.House_Robber_III]
这个小偷又偷出新花样了,沿着二叉树开始偷.
Example 1:
Input: [3,2,3,null,3,null,1]
3
/ \
2 3
\ \
3 1
Output: 7 (3 + 3 + 1 = 7)
Example 2:
Input: [3,4,5,1,3,null,1]
3
/ \
4 5
/ \ \
1 3 1
Output: 9 (4 + 5 = 9)
题目中给的例子看似好像是要每隔一个偷一次,但实际上不一定只隔一个,比如:
4
/
1
/
2
/
3
如果隔一个偷,那么是4+2=6,其实最优解应为4+3=7,隔了两个。
定义子函数算出是否抢当前节点
返回一个大小为2的一维数组tmp_result,其中tmp_result[0]表示不包含当前节点值的最大值,tmp_result[1]表示包含当前值的最大值,那么我们在遍历某个节点时,首先对其左右子节点调用递归函数,分别得到包含与不包含左子节点值的最大值,和包含于不包含右子节点值的最大值,那么当前节点的tmp_result[0]就是左子节点两种情况的较大值加上右子节点两种情况的较大值,tmp_results[1]就是不包含左子节点值的最大值加上不包含右子节点值的最大值,和当前节点值之和,返回即可
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def rob(self, root):
"""
:type root: TreeNode
:rtype: int
"""
if not root:
return 0
result = max(self.subRob(root))
return result
def subRob(self, root):
left_val = [0, 0]
right_val = [0, 0]
tmp_result = [0, 0]
if not root:
return [0, 0]
if root.left:
left_val = self.subRob(root.left)
if root.right:
right_val = self.subRob(root.right)
# not rob root
tmp_result[0] = max(left_val[0], left_val[1]) + max(right_val[0], right_val[1])
# rob root
tmp_result[1] = root.val + left_val[0] + right_val[0]
return tmp_result
reference
更多精彩文章,欢迎关注公众号“Li的白日呓语”。