“打家劫舍”系列总结,偷不偷这个房间呢?(Java实现)

追求适度,才能走向成功;人在顶峰,迈步就是下坡;身在低谷,抬足既是登高;弦,绷得太紧会断;人,思虑过度会疯;水至清无鱼,人至真无友,山至高无树;适度,不是中庸,而是一种明智的生活态度。

导读:本篇文章讲解 “打家劫舍”系列总结,偷不偷这个房间呢?(Java实现),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

目录

前言

一、打家劫舍 ——>房子是线性的

1.1、dp定义

1.2、递推公式

1.3、初始化

1.4、遍历顺序

1.5、解题代码

二、打家劫舍II ——>房子是环型

2.1、分析

2.2、解题代码

三、打家劫舍III ——>房子是树形

3.1、dp含义

3.2、递推公式

3.3、解题代码

小结


前言

        本篇题目虽然可以使用其他方法,如暴力枚举、贪心来解,但是只是针对特定场景的问题才可以,所以这里我将用动态规划的方法来教你,怎么偷,才能偷到最多钱~


一、打家劫舍 ——>房子是线性的

题目描述:

“打家劫舍”系列总结,偷不偷这个房间呢?(Java实现)

题目来源:198. 打家劫舍

1.1、dp定义

分析:

        我们需要考虑到每一个房间,并且得出可以偷盗的最大金额,因此dp数组的数值就是我们能偷到的最大金额,就对应第几天~

dp定义:

dp[i]:考虑偷第i个房间(包括之前的房间),可以偷盗的最大金额。

1.2、递推公式

分析:

dp[i]有几种状态?从哪些状态可以推出dp[i]?

        一个房间无非就是两种状态,偷或者不偷,根据题目描述,不能连续偷盗两个相邻的房间,所以,当前这个房间如果我们不去偷他,那么当前所拥有的最大金额就是前一个状态dp[i – 1]这个时候所拥有的最大金额,如果要偷,那么前一天一定不能偷,那么当前所拥有的最大金额就是dp[i – 2] + nums[i],也就是不考虑昨天,加上今天一定偷所能获得的最大金额

递推公式:

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

1.3、初始化

分析:

        从递推公式中可以看出需要用到两个状态,dp[i – 1]和dp[i – 2],将这两个状态一直推下去,就可以知道我们需要初始化的是dp[0]和dp[1];

dp[0]表示考虑偷第1个房间,那么现在总共就一个房间,肯定是要偷的。

dp[1]表示考虑偷第2个房间(包括之前的房间),那么现在总共就两个房间,由于不能连续偷相邻房间,因此就偷这两个房间价值最大的。

初始化:

dp[0] = nums[0];

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

1.4、遍历顺序

由递推公式可以知道,要推出当前dp[i],需要知道前两天的状态,所有从左往右遍历即可。

1.5、解题代码

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

二、打家劫舍II ——>房子是环型

题目描述:

“打家劫舍”系列总结,偷不偷这个房间呢?(Java实现)

题目来源: 213. 打家劫舍 II

2.1、分析

 思考:

        虽然房子是一个环型,无非就是要多考虑一个问题,因为不能连续偷盗两个相邻的房间,所以第一个房间和最后一个房间只能偷一个,那么我们可以把它想象成两个线性的结构,如下图:

“打家劫舍”系列总结,偷不偷这个房间呢?(Java实现)

 

本题与“打家劫舍”这道题一样,最后问的都是所能偷盗的最大价值,因此就可以把题目所给的房间截取成上图中的两端,然后分别用“打家劫舍”那道题的方式进行处理,最后比较出 “偷第一个不偷最后一个” 和 “不偷第一个偷最后一个” 的最大值,就是我们要求的最大价值。

2.2、解题代码

class Solution {
    public int rob(int[] nums) {
        int len = nums.length;
        if(len == 1) return nums[0];
        if(len == 2) return Math.max(nums[0], nums[1]);
        int first = robI(Arrays.copyOfRange(nums, 0, len - 1));
        int last = robI(Arrays.copyOfRange(nums, 1, len));
        return Math.max(first, last);
    }

    private int robI(int[] nums) {
        int len = nums.length;
        if(len == 1) return nums[0];
        if(len == 2) return Math.max(nums[0], nums[1]);
        int[] dp = new int[len];
        dp[0] = nums[0];
        dp[1] = Math.max(nums[0], nums[1]);
        for(int i = 2; i < len; i++) {
            dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
        }
        return dp[len - 1];
    }
}

三、打家劫舍III ——>房子是树形

题目描述:

“打家劫舍”系列总结,偷不偷这个房间呢?(Java实现)

“打家劫舍”系列总结,偷不偷这个房间呢?(Java实现) 

题目来源:337. 打家劫舍 III 

3.1、dp含义

分析:

        房子是一个树形,我们可以采取一个后续遍历的方式(后面会讲原因),去遍历这颗树,最后得出最大值;

房子只有两种状态,偷或者不偷,我们可以用Pair键值对的形式来保存这两种状态,我们只需要在后序遍历的过程中写下Pair,那么每一个结点都有一个Pair这两种状态,因此可以延续。

初始化:

Pair<Integer, Integer> dp :表示当前房屋偷(getValue())和不偷(getKey())两种最大价值。

最后返回两者最大值即可。

3.2、递推公式

分析:

房子只有偷和不偷两种状态,那么

1.如果偷当前房子,就一定不可以偷当前结点的两个子节点,因此偷当钱房屋的最大价值就是,不偷两个子节点的最大价值(使用后续遍历的原因:这里的子节点的值从何而来,就是需要后序遍历先得到值,现在才能用子节点的返回值) + 当前结点的价值 ——> dpLeft.getKey() + dpRight.getKey() + root.val;

2.如果不偷当前房子,那么两个子节点可以偷也可以选择不偷(比如就拿其中一个子节点来说,这一个子节点可以偷也可以不偷,需要从这两种状态种选出一个最大值),分别得出一个最大值即可,然后求和就是当前所拥有的最大价值 ——>Math.max(dpLeft.getKey(), dpLeft.getValue()) + Math.max(dpRight.getValue(), dpRight.getKey());

动态转移方程:

int val1 = Math.max(dpLeft.getKey(), dpLeft.getValue()) +
        Math.max(dpRight.getValue(), dpRight.getKey());//不偷当前结点
int val2 = dpLeft.getKey() + dpRight.getKey() + root.val;//偷当前结点

3.3、解题代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {

    public int rob(TreeNode root) {
        Pair<Integer, Integer> dp = dfs(root);
        return Math.max(dp.getKey(), dp.getValue());
    }

    private Pair<Integer, Integer> dfs(TreeNode root) {
        if(root == null) {
            return new Pair<>(0, 0);
        }
        Pair<Integer, Integer> dpLeft = dfs(root.left);
        Pair<Integer, Integer> dpRight = dfs(root.right);
        int val1 = Math.max(dpLeft.getKey(), dpLeft.getValue()) +
        Math.max(dpRight.getValue(), dpRight.getKey());//不偷
        int val2 = dpLeft.getKey() + dpRight.getKey() + root.val;
        return new Pair<>(val1, val2);
    }

}

小结

小偷这么聪明,为啥还要来偷?


“打家劫舍”系列总结,偷不偷这个房间呢?(Java实现)

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/130378.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!