LeetCode | 史上最佳House Robber 系列总结(python版)

打家劫舍

[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的白日呓语”。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值