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

文章目录

      • 198.打家劫舍
      • 213.打家劫舍II
      • 337.打家劫舍III

198.打家劫舍

  • 题目链接:代码随想录

  • 解题思路:
    1.dp[i]:考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i] 只是考虑,不一定偷
    2.递推公式:dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]),根据选不选i位置,由两个方面推导而来
    3.dp数组如何初始化。因为递推公式是dp[i-1]和dp[i-2],所以初始化要考虑dp[0]和dp[1]
    因为要取最大值,所以dp[0] 一定是 nums[0],dp[1]就是nums[0]和nums[1]的最大值即:dp[1] = max(nums[0], nums[1])
    4.遍历顺序:从前向后。因为后面状态由前面推出来

public int rob(int[] nums) {

    if(nums.length == 1){
        return nums[0];
    }

    //1.定义dp数组,dp数组表示dp[i],考虑i位置的情况下能打劫到的最大价值
    //这里dp[i]中的i代表不一定选第i个位置的数字
    int[] dp = new int[nums.length];

    //2.初始化
    dp[0] = nums[0];
    dp[1] = Math.max(nums[0], nums[1]);
    //3.遍历
    for (int i = 2; i < dp.length; i++) {
        //根据选不选i位置的数值
        //选,只能加上dp[i-2]的数值
        dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);
    }

    //4.最后返回递推的结果
    return dp[nums.length - 1];
}

213.打家劫舍II

关键点:将环形问题的情况分解成线性问题的情况,进而求解

  • 题目链接:代码随想录

  • 解题思路:
    ①根据环形问题,分为三种情况一种不考虑首尾,一种考虑首不考虑尾,最后一种考虑尾不考虑首
    后两种情况考虑首不考虑尾就包含了不考虑首尾的问题,因此只用将最后两种打家劫舍问题求一个和即可
    编写一个有参数的打家劫舍函数,里面进行初始化和相应范围的打家劫舍问题的分析。
    要想编写容易,要借用自动扩容的ArrayList数组,dp范围和nums范围和位置一致

  • 三种状态

    代码随想录算法训练营第四十八天| 198.打家劫舍、213.打家劫舍II、337.打家劫舍III_第1张图片 代码随想录算法训练营第四十八天| 198.打家劫舍、213.打家劫舍II、337.打家劫舍III_第2张图片 代码随想录算法训练营第四十八天| 198.打家劫舍、213.打家劫舍II、337.打家劫舍III_第3张图片
public int rob(int[] nums) {
    int len = nums.length;
    //保证len从3开始
    if(len == 1){
        return nums[0];
    }
    if(len == 2){
        return Math.max(nums[0], nums[1]);
    }

    return Math.max(robAction(nums, 0, len - 2),robAction(nums, 1, len - 1));

}

/**
     * 考虑[start,end]位置房屋的打家劫舍问题
     * @param nums
     * @param start
     * @param end
     * @return
     */
private int robAction(int[] nums, int start, int end) {
    //定义dp数组的时候,要选用可扩容的ArrayList,因为要保证dp数组下标值和nums数组下标值一样
    List<Integer> dp = new ArrayList<>(nums.length);
    for(int i = 0;i < dp.size();i++){
        dp.add(0);
    }
    //初始化
    dp.set(start, nums[start]);
    dp.set(start + 1, Math.max(nums[start], nums[start + 1]));
    for (int i = start + 2; i <= end; i++) {
        dp.set(i, Math.max(dp.get(i - 2) + nums[i], dp.get(i - 1)));
    }

    return dp.get(end);
}
public static void main(String[] args) {
    List<Integer> dp = new ArrayList<>(2);
    System.out.println(dp.size());//0  这里只有首次添加元素之后,size1才变化
    dp.add(0, 0);
    System.out.println(dp.size());//1
}

337.打家劫舍III

本题是树形dp的入门级别的题目,通过返回dp数组来保存遍历状态 也称状态标记递归,通过一个标记,来记录遍历过程中的最大值

  • 题目链接:代码随想录

  • 解题思路:
    1.确定递归函数的参数和返回值
    那么返回值就是一个长度为2的dp数组。dp[0]表示不偷当前节点情况下的金钱dp[1]偷当前节点情况下的金钱,参数为当前节点,将当前节点偷与不偷得到的金钱返回给上一层
    在递归过程中,系统栈会保存每一层递归的参数,因此每一个节点的dp数组经过分析汇聚给root
    2.终止条件
    遇到空节点,直接返回本层偷的结果{0,0}
    3.确定遍历顺序:
    采用后序遍历,因为要根据左右节点的偷的金钱状态,根节点判断当前根偷还是不偷
    这种需要依靠状态的,都需要采取后序遍历
    4.确定单层递归逻辑:
    如果偷当前节点,那么dp[1] = root.val + 左右节点不偷的金钱
    如果不偷当前节点,那么左右节点可以偷,也可以不偷,因此dp[0] = max([0],[1])(左右节点)

  • 推导过程:

    代码随想录算法训练营第四十八天| 198.打家劫舍、213.打家劫舍II、337.打家劫舍III_第4张图片
public int rob(TreeNode root) {
    int[] dp = robAction1(root);

    return Math.max(dp[0], dp[1]);
}


/**
     * 递归树
     * @param root
     * @return 一个dp一维数组
     */
private int[] robAction1(TreeNode root){
    //本层dp状态数组
    int[] dp = new int[2];
    //终止条件
    if(root == null){
        return dp;
    }

    int[] leftDp = robAction1(root.left);
    int[] rightDp = robAction1(root.right);

    //本根不偷
    dp[0] = Math.max(leftDp[0], leftDp[1]) + Math.max(rightDp[0], rightDp[1]);
    //本根偷
    dp[1] = root.val + leftDp[0] + rightDp[0];

    //返回本层偷与不偷的状态
    return dp;
}

你可能感兴趣的:(代码随想录一刷,算法,数据结构,java)