java数据结构与算法刷题-----LeetCode213. 打家劫舍 II

java数据结构与算法刷题目录(剑指Offer、LeetCode、ACM)-----主目录-----持续更新(进不去说明我没写完):https://blog.csdn.net/grd_java/article/details/123063846

很多人觉得动态规划很难,但它就是固定套路而已。其实动态规划只不过是将多余的步骤,提前放到dp数组中(就是一个数组,只不过大家都叫它dp),达到空间换时间的效果。它仅仅只是一种优化思路,因此它目前的境地和线性代数一样----虚假的难。

  1. 想想线性代数,在国外留学的学生大多数不觉得线性代数难理解。但是中国的学生学习线性代数时,完全摸不着头脑,一上来就是行列式和矩阵,根本不知道这玩意是干嘛的。
  2. 线性代数从根本上是在空间上研究向量,抽象上研究线性关系的学科。人家国外的教科书都是第一讲就帮助大家理解研究向量和线性关系。
  3. 反观国内的教材,直接把行列式搞到第一章。搞的国内的学生在学习线性代数的时候,只会觉得一知半解,觉得麻烦,完全不知道这玩意学来干什么。当苦尽甘来终于理解线性代数时干什么的时候,发现人家国外的教材第一节就把这玩意讲清楚了。你只会大骂我们国内这些教材,什么狗东西(以上是自己学完线性代数后的吐槽,我们同学无一例外都这么觉得)。

而我想告诉你,动态规划和线性代数一样,我学完了才知道,它不过就是研究空间换时间,提前将固定的重复操作规划到dp数组中,而不用暴力求解,从而让效率极大提升。

  1. 但是网上教动态规划的兄弟们,你直接给一个动态方程是怎么回事?和线性代数,一上来就教行列式和矩阵一样,纯属恶心人。我差不多做了30多道动态规划题目,才理解,动态方程只是一个步骤而已,而这已经浪费我很长时间了,我每道题都一知半解不理解,过程及其痛苦。最后只能重新做。
  2. 动态规划,一定是优先考虑重复操作与dp数组之间的关系,搞清楚后,再提出动态方程。而你们前面步骤省略了不讲,一上来给个方程,不是纯属扯淡吗?
  3. 我推荐研究动态规划题目,按5个步骤,从上到下依次来分析
  1. DP数组及下标含义
  2. 递推公式
  3. dp数组初始化
  4. 数组遍历顺序(双重循环及以上时,才考虑)
  5. dp数组打印,分析思路是否正确(相当于做完题,检查一下)

    这道题是198题的衍生题,在198题的基础上,增加了一个条件,就是第一个房子和最后一栋房子是挨着的。如果偷了第一个房子,那么最后一个房子就不能偷。除此之外没有任何不同
可以先参考🏆LeetCode198. 打家劫舍https://blog.csdn.net/grd_java/article/details/135401880
先理解题目细节

想象这样一个场景,我们是小偷,从第一栋挨个考虑偷还是不偷(注意:不考虑后面的,只考虑当前这栋和之前的),如果偷,那就是身上已经偷了多少+这一栋能偷多少。如果不偷,那就是身上已经偷了多少就是还是多少。但是相邻的不能同时偷,主要是第一栋如果偷了,最后一栋就不能偷。


  1. 题目给我们的数组代表每个房子有多少钱,而且相邻的房子不能都偷,只能偷不相邻的。而且首尾相邻,若要偷第一栋,则最后一栋不能偷,不偷第一栋则最后一栋可以偷
  2. 例如2,3,2 这个序列,显然不触发警报的前提下(不偷相邻的)----偷第二个为能偷到的最多的钱。不可以偷第一栋和第3栋=4元,因为首尾相邻
  3. 怎么偷才能偷到最多呢?当然是保证最大获利情况下,能偷的都偷了,而且不能触发警报。
  4. 我们每个房子都单独考虑,偷还是不偷,而且只需考虑它前面的房子,因为我们要能偷的都偷
  1. 若要偷当前房子:那么,前面相邻的不能考虑,只能考虑再前面一个,也就是当前房子的金额,加上除了前面一个相邻的房子外,已经偷了多少。
  2. 若不偷当前房子:那么,前面偷了多少,到这个房子就还有多少,金额不变
解题思路
  1. 暴力求解的思想,就是回溯算法,枚举每一种情况,拿到最大值,显然会做大量无效运算。
  2. 但是如果我们预先将其存储到dp数组,就可以直接通过dp[x], 获取dp数组中指定位置x的体力花费,而不用枚举。典型的动态规划题目
动态规划思考5步曲
  1. DP数组及下标含义

我们要求出的是到了某个房子后最大情况下偷了多少钱,那么dp数组中存储的就是最大情况下偷了多少钱。要求出谁的最大情况下偷了多少。显然是到达某个房子后,那么下标就是代表现在到了哪个房子,也就是代表到了某一栋房子后的最大已偷取的金额。显然,只需要一个下标即可表示,故这道题的dp数组只需要一维数组

  1. 递推公式
  1. 由题意可知,每个房子都可以选择偷与不偷,选择最大值。而我们选择要偷的第一个房子肯定要偷它,才能获得最大值。而第二个房子,因为它前面没有不相邻的房子,所以它要么不偷,也就是只偷第一个房子。要么选择偷第二个房子,而第一个房子不偷,然后选择最大情况。

故:第一个房子固定为F(start) = 第一个房子金额。第二个固定为F(start+1) = max(第一个房子金额,第二个房子金额)

注意:上面说的第一个房子,是相对于小偷来说,他偷的第一个房子。例如整个街区中,我们不偷第一栋,那么就从第二栋开始偷。对于小偷来说,这第二栋就是他偷的第一个房子,记为start.

  1. 之后每一栋,都需要判断它偷还是不偷,以及前面不相邻的房子。如果偷,就要考虑前面不相邻的+自己这栋能偷多少钱。不偷,那么前一栋相邻的它肯定考虑一下。而前面的房子怎么考虑的是前面房子的事,它们也考虑过了偷还是不偷。我们只考虑当前这栋是偷,还是不偷
  2. 因此,可以得到,从第三栋开始(相对于小偷来说),递推公式为偷或者不偷,选择最大值: F(n) = max( F(n-2)+nums[n] , F(n-1) )
  1. dp数组初始化
  1. 数组遍历顺序(因为这个数列是一维的,只需要一重循环,无需考虑这个)
  2. 打印dp数组(自己生成dp数组后,将dp数组输出看看,是否和自己预想的一样。)
代码:时间复杂度O(n).空间复杂度O(n)
java 复制代码
class Solution {
    public int rob(int[] nums) {
        int length = nums.length;//房子的数量
        if(length == 1) return nums[0];//如果只有一栋,那么肯定偷它,返回即可
        else if (length == 2) return Math.max(nums[0],nums[1]);//如果只有两栋,那么相邻的不能偷,只能选一栋钱多的
        //如果房子很多,那么分为两种情况①:第一栋要偷,最后一栋不能偷。②:第一栋不偷,最后一栋可以偷
        return Math.max(robRange(nums,0,length-2), robRange(nums,1,length-1));
    }
    public int robRange(int[] nums, int start, int end){
        /**进入小偷视角,他第一个偷的房子start,为他眼里的第一栋。 而实际在街区可能是第一栋,也可能是第二栋**/
        int dp[] = new int [nums.length];//dp数组
        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];
    }
}
学有余力的同学可以尝试这个方法,将空间复杂度变为常数级-----------------代码:时间复杂度O(n).空间复杂度O(1)

将dp数组优化掉,换成3个变量,滚动执行。将dp[0]换成first。dp[1]换成second. 也就是first永远指向当前栋的前面不相邻的,second永远指向前面相邻的。

java 复制代码
class Solution {
    public int rob(int[] nums) {
        int length = nums.length;//房子的数量
        if(length == 1) return nums[0];//如果只有一栋,那么肯定偷它,返回即可
        else if (length == 2) return Math.max(nums[0],nums[1]);//如果只有两栋,那么相邻的不能偷,只能选一栋钱多的
        //如果房子很多,那么分为两种情况①:第一栋要偷,最后一栋不能偷。②:第一栋不偷,最后一栋可以偷
        return Math.max(robRange(nums,0,length-2), robRange(nums,1,length-1));
    }
    public int robRange(int[] nums, int start, int end){
        /**进入小偷视角,他第一个偷的房子start,为他眼里的第一栋。 而实际在街区可能是第一栋,也可能是第二栋**/
        int dp[] = new int [nums.length];//dp数组
        int first = nums[start];//只考虑第一栋的情况下,第一栋只有偷了,才是最好的选择
        int second = Math.max(nums[start],nums[start+1]);//只考虑前两栋的情况下,可以选择偷第一栋,或者偷第二栋
        //剩下的都需要考虑①:偷还是不偷,②:如果偷=前面不相邻的已经偷了多少+当前这栋有多少。③:如果不偷,那么前面已经偷了多少,就是多少
        for(int i = start+2; i<= end;i++){
            int temp = second;
            second = Math.max(first+nums[i],second);
            first = temp;
        }
        return second;
    }
}
相关推荐
吾日三省吾码2 小时前
JVM 性能调优
java
LNTON羚通2 小时前
摄像机视频分析软件下载LiteAIServer视频智能分析平台玩手机打电话检测算法技术的实现
算法·目标检测·音视频·监控·视频监控
弗拉唐3 小时前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
oi774 小时前
使用itextpdf进行pdf模版填充中文文本时部分字不显示问题
java·服务器
少说多做3434 小时前
Android 不同情况下使用 runOnUiThread
android·java
知兀4 小时前
Java的方法、基本和引用数据类型
java·笔记·黑马程序员
哭泣的眼泪4084 小时前
解析粗糙度仪在工业制造及材料科学和建筑工程领域的重要性
python·算法·django·virtualenv·pygame
清炒孔心菜4 小时前
每日一题 LCR 078. 合并 K 个升序链表
leetcode
蓝黑20204 小时前
IntelliJ IDEA常用快捷键
java·ide·intellij-idea