力扣198题、213题、337题(打家劫舍)

198、打家劫舍

基本思想:

动态规划

具体实现:

1、确认状态

dp[i]=前 i 个房子在满足条件下的能偷窃到的最高金额。

2、状态转移

由于不可以在相邻的房屋闯入,所以在当前位置 i 房屋可盗窃的最大值,

要么就是 i-1 房屋可盗窃的最大值,

要么就是 i-2 房屋可盗窃的最大值加上当前房屋的值,二者之间取最大值

dp[i] = max(dp[i-1],nums[i]+dp[i-2])

3、计算顺序

从前到后遍历

4、初始状态

从递推公式dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);可以看出,递推公式的基础就是dp[0] 和 dp[1]

从dp[i]的定义上来讲,dp[0] 一定是 nums[0],dp[1]就是nums[0]和nums[1]的最大值即:dp[1] = max(nums[0], nums[1]);

5、举例推导dp数组

 

 

代码:

class Solution {
    public int rob(int[] nums) {
        if (nums == null || nums.length == 0) return 0;
        if (nums.length == 1) return nums[0];
         int[] dp = new int[nums.length];
         dp[0] = nums[0];
         dp[1] = Math.max(dp[0], nums[1]);
         for (int i = 2; i < nums.length; i++){
             dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
         }

         return dp[nums.length - 1];
    }
}

 

 

213、打家劫舍II

基本思想:

与上一题的区别是成环了

具体实现:

1、考虑取第一间房子的钱,不取最后一间房子的钱(是考虑,不是非要取)

 

 

2、考虑取最后一间房子的钱,不取第一间房子的钱

 

 

3、考虑第一间和最后一件都不取

 

 

情况1,2包括了情况3

代码:

class Solution:
    def rob(self, nums: List[int]) -> int:
        def my_rob(nums):
            cur, pre = 0, 0
            for num in nums:
                cur, pre = max(pre + num, cur), cur
            return cur
        if len(nums) != 1:
            return max(my_rob(nums[:-1]),my_rob(nums[1:]))
        else:
            return nums[0]

 

class Solution {
    public int rob(int[] nums) {
        if (nums.length == 0) return 0;
        if (nums.length == 1) return nums[0];
        int result1 = robRange(nums, 0, nums.length - 2);
        int result2 = robRange(nums, 1, nums.length - 1);
        return Math.max(result1, result2);
    }

    public int robRange(int[] nums, int start, int end) {
        if (end == start) return nums[start];
         int[] dp = new int[nums.length];
         dp[start] = nums[start];
         dp[start + 1] = Math.max(nums[start], nums[start + 1]);
         for (int i = start + 2; i <= end; i++){
             dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
         }

         return dp[end];
    }
}

 

 

337、打家劫舍III

基本思想:

动态规划

后序遍历,因为通过递归函数的返回值来做下一步计算。

具体实现:

1、确认状态

求一个节点 偷与不偷的两个状态所得到的金钱,那么返回值就是一个长度为2的数组。

dp数组(dp table)以及下标的含义:下标为0记录不偷该节点所得到的的最大金钱,下标为1记录偷该节点所得到的的最大金钱。

长度为2的数组怎么标记树中每个节点的状态呢?

在递归的过程中,系统栈会保存每一层递归的参数。

2、确定终止条件

遍历的过程中,如果遇到空节点的话,很明显,无论偷还是不偷都是0,所以就返回

3、遍历顺序

首先明确的是使用后序遍历。 因为通过递归函数的返回值来做下一步计算。

通过递归左节点,得到左节点偷与不偷的金钱。

通过递归右节点,得到右节点偷与不偷的金钱。

4、单层递归逻辑

任何一个节点能偷到的最大钱的状态可以定义为

如果是偷当前节点,那么左右孩子就不能偷,val1 = cur.val + left[0] + right[0]; (如果对下标含义不理解就在回顾一下dp数组的含义)

如果不偷当前节点,那么左右孩子就可以偷,至于到底偷不偷一定是选一个最大的,所以:val2 = max(left[0], left[1]) + max(right[0], right[1]);

最后当前节点的状态就是{val2, val1}; 即:{不偷当前节点得到的最大金钱,偷当前节点得到的最大金钱}

 

 

 

 

 代码:

class Solution {
    public int rob(TreeNode root) {
        int[] res = robAction(root);
        return Math.max(res[0], res[1]);
    }
    public int[] robAction(TreeNode root) {
        int res[] = new int[2];
        if (root == null) return res;
        int[] left = robAction(root.left);
        int[] right = robAction(root.right);
        res[0] = Math.max(left[0], left[1]) + Math.max(right[0] , right[1]);
        res[1] = root.val + left[0] + right[0];
        return res;
    }
}

 

posted @ 2021-03-24 11:23  最近饭吃的很多  阅读(66)  评论(0编辑  收藏  举报