代码随想录算法训练营第47天 || 198.打家劫舍 || 213.打家劫舍II || 337.打家劫舍III

代码随想录算法训练营第47天 || 198.打家劫舍 || 213.打家劫舍II || 337.打家劫舍III

198.打家劫舍

题目介绍

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例 1:

输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4 。

示例 2:

输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
     偷窃到的最高金额 = 2 + 9 + 1 = 12 。

个人思路

动规五部曲

  1. 确定dp数组及其下标含义

    dp[j]:从下标0到下标j的房间中,能偷的最大金额

  2. 确定递推公式

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

    表示偷或不偷当前房间对应的金额,其中取较大的那个;

  3. 初始化dp数组

    dp[0] = nums[0];
    dp[1] = Integer.max(dp[0], nums[1]);
    

    适应递推公式,从而初始化前两个dp数组元素

  4. 确定遍历顺序

    正序遍历即可(结合递推公式)

  5. 打印dp数组检验

代码:

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

213.打家劫舍II

题目介绍

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。

示例 1:

输入:nums = [2,3,2]
输出:3
解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。

示例 2:

输入:nums = [1,2,3,1]
输出:4
解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4 。

示例 3:

输入:nums = [1,2,3]
输出:3

思路解析

这道题看似只加了一个循环条件,但确实比较难考虑了不少。我一开始就想通过做标记的办法,决定最后加不加最后一个元素,过程中还得判断加与不加相等的情况(并且尽量取能加最后一个元素的情况)。但是发现会出现一种情况,最后一个元素大于第一个元素,但根据前面的取法,最后一位已经取不到了,导致答案错误。然后我就想能不能一开始比较始末位置的值,决定从大的一边去遍历,但这样要写两套相似的逻辑代码(当然可以函数封装,遍历+1/-1用一个变量存储即可),不过还是较为麻烦

题解解析

这个问题大致可以分为三种情况

  • 去掉头尾的非环问题
  • 去掉头的非环问题
  • 去掉尾的非环问题

实际上情况2、3已经包括1了,因为dp[j]:表示的就是从头到j下标的最大偷盗价值,如果边界取不到,那就刚好等同于情况一。所以本题,我们只需要考虑后两种情况,然后取最大值即可(封装函数,调用两次,比较两个结果)

动规五部曲

  1. 确定dp数组及其下标含义

    dp[j]:前j+1家能偷到的最大价值

  2. 确定递推公式

    dp[i] = Integer.max(dp[i - 1], dp[i - 2] + nums[i+left]);
    

    还是与之前一样,偷与不偷当前房子的比较

  3. 初始化dp数组

    dp[0] = nums[left];
    dp[1] = Integer.max(dp[0], nums[left+1]);
    
  4. 确定遍历顺序

    正序遍历即可

  5. 打印dp数组检验

代码:

class Solution {
    public int rob(int[] nums) {
        if (nums.length == 1)
            return nums[0];
        int rob_max1 = rob_(nums, 1, nums.length - 1);
        int rob_max2 = rob_(nums, 0, nums.length - 2);
        return Integer.max(rob_max1,rob_max2);
    }
    public int rob_(int[] nums,int left,int right){
        if (right - left == 0)
            return nums[left];
        int[] dp = new int[right-left+1];
        dp[0] = nums[left];
        dp[1] = Integer.max(dp[0], nums[left+1]);
        for (int i = 2; i < dp.length; i++) {
            dp[i] = Integer.max(dp[i - 1], dp[i - 2] + nums[i+left]);
        }
        return dp[dp.length - 1];
    }
}

337.打家劫舍III

题目介绍

小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root

除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。

给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额

示例 1:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AM0FWPIY-1677057886054)(null)]

输入: root = [3,2,3,null,3,null,1]
输出: 7 
解释: 小偷一晚能够盗取的最高金额 3 + 3 + 1 = 7

示例 2:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ubZdCCUF-1677057886010)(null)]

输入: root = [3,4,5,1,3,null,1]
输出: 9
解释: 小偷一晚能够盗取的最高金额 4 + 5 = 9

思路解析

结合动态规划和二叉树遍历的知识,每个结点都有偷和不被偷两种情况,那么我们分别记录每个节点的这两种情况的最优解,由下往上返回最优解,从子树最优得到全局最优(有点小贪心吧)

动规五部曲、递归三部曲

  1. 确定参数及返回值

    参数:结点

    返回值:int[2],偷与不偷当前结点的最大金额

  2. 确定终止条件

    if (root == null)
        return new int[]{0, 0};
    
  3. 确定递归顺序

    后序遍历

  4. 确定单层递归逻辑

    1. 确定dp数组及其下标含义

      dp[0]:表示已当前结点为根的二叉树中,当前结点的最大金额

      dp[1]:表示已当前结点为根的二叉树中,不偷当前结点的最大金额

    2. 确定递推公式

      dp[0] = root.val + dp_left[1] + dp_right[1];//偷自己的情况下,子树只能取不偷的情况
      //不偷自己的情况下,取偷或不偷左右孩子的最大值
      dp[1] = Integer.max(dp_left[0], dp_left[1]) + Integer.max(dp_right[0], dp_right[1]);
      
  5. 最后取偷或不偷根节点的最大金额即可

class Solution {
    public int rob(TreeNode root) {
        int[] dp = traversal(root);
        return Integer.max(dp[0], dp[1]);
    }

    public int[] traversal(TreeNode root) {
        if (root == null)
            return new int[]{0, 0};
        int[] dp_left = traversal(root.left);
        int[] dp_right = traversal(root.right);
        int[] dp = new int[2];//0表示偷自己,1表示不偷自己
        dp[0] = root.val + dp_left[1] + dp_right[1];
        //不偷自己的情况下,取偷或不偷左右孩子的最大值
        dp[1] = Integer.max(dp_left[0], dp_left[1]) + Integer.max(dp_right[0], dp_right[1]);
        return dp;
    }
}

你可能感兴趣的:(算法,动态规划,leetcode)