算法题解记录21+++打家劫舍(百日筑基)

题目描述:

        题目难度:中等

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

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

示例 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 <= nums.length <= 100
  • 0 <= nums[i] <= 400

解题准备:

        1.了解题目可能的基础操作:既然要求找到偷窃最优的方法,假设我们采用枚举,可以发现,涉及了大量查找操作。

        2.理解题意:本题的要求,是能偷盗最多的金额;本题的约束,是偷盗的房屋,不能相邻。

        3.动态规划算法:

  1. 基本思想:将最优解问题,转化为子问题最优解问题。
  2. 适用情形:
    1. 要求输出是最优解;                        
    2. 主问题的最优解,与子问题相关;
    3. 如果从头遍历,可以枚举解决,不过效率极低,而且代码很难编写;
  3. 典型例题:爬楼梯、背包、打家劫舍

解题思路:

        不从编码的角度出发,朴素的思路是,找出所有可能的组合,放在一起进行比较。

        思路---比较所有组合:

                1.从0出发,偷盗第1家、第3家、第5家、第7家……【1、3、5、7……】

                2.从0出发,偷盗第1家、第3家、第6家……【1、3、6……】

                ……

                3.从0出发,偷盗第1家、第4家、第6家、第8家……【1、4、6、8……】

                ……

                4.从0出发,偷盗第2家、第4家、第6家……【2、4、6……】

        虽然小偷的运算能力可能比较差,怎么算也算不到头,可是我们拥有电脑,最终一定能得到结果。

        不过问题是:我们应该迭代多少次?

        对于2家,组合数只有2个(偷1,或者偷2)

        对于3家,组合数只有2个(偷1、3,或者偷2)

        对于4家,组合数有3个(偷1、3,偷2、4,偷1、4)

        ……

        假设我们解决了迭代次数的问题,第2个问题是:如何得到某一个组合偷盗的钱?

        比如8家,我们选择1、3、5、7,第一次迭代,容易计算。

        第二次,1、3、5、8,你应该如何表示这些不同组合?

        两个指针好像明显不够,但是如果用一个boolean类型的数组,标识偷盗过的与没偷盗过的,又有点难以判断。

        同时这也是第3个问题:你无法判断前面偷盗过的房子,与你正准备偷盗的房子是否相邻。

        这些问题单个还比较好解决,但是整合起来就非常难处理。

        思路---贪心选择最优:

                也许你会出现第二个想法,对于一组数据,我在每一步都拿到最大的数,不就可以解决了吗?

                如何判断呢?

                以我当时的思路举例,我使用了3个指针,lp、ptr、rp,分别指向3个相邻的房间,此外,用一个boolean变量flag,表示lp的前一个房间偷盗过没。

                1.由于3个指针,明显1、2个房间不能适用,所以特殊判断。

                2.初始化3个指针,lp=0,ptr=1,rp=2;

                3.每一步判断谁最大:

【真的天才,这个列表没法对齐,将就看吧】

  •         如果lp最大,则判断前一个房间flag偷过没,没偷过,就偷掉lp【因为,既然lp最大,就算存在ptr+rp>lp,也不能连续偷】
    • 如果ptr最大,不用说,必偷【因为lp、rp都没偷过,就算rp的下一个房间大于ptr,也不影响偷】
    • 如果rp最大,就判断lp的前一个房间flag有没有偷过,没偷过,就偷掉flag【因为,既然rp大了,那ptr偷不得,利用一下lp,看lp需不需要偷】

               4.循环下去,每次3个指针步进1。【结束条件:rp>数组len】

                5.在外部再判断一次,如果ptr没偷过,就把rp偷了。

        理解起来不困难,然而在实际操作中,会出现意料不到的情况。【算法不具备循环不变性】

        第一次判断,lp、ptr、rp中,必然有lp或者ptr被偷。

        如果ptr被偷,步进1步,flag的标识作用就消失了。

        如果lp、ptr、rp指向的元素,有相同数,比如8、9、9,就很可能失效。

我的第一次尝试是用这个思路写的,通过了部分测试用例,代码如下:

class Solution {
    public int rob(int[] nums) {
        if(nums.length==1){
            return nums[0];
        }else if(nums.length==2){
            return nums[0]>nums[1]?nums[0]:nums[1];
        }else if(nums.length==3){
            return Math.max(nums[1], nums[0]+nums[2]);
        }else if(nums.length==4){
            int t1=nums[0]+nums[2];
            t1=t1>nums[0]+nums[3]?t1:nums[0]+nums[3];
            int t3=nums[1]+nums[3];

            return Math.max(t1, t3);
        }

        int lp=0, ptr=1, rp=2;
        int res=0;
        int temp;
        boolean flag=false;

        while(rp<nums.length){
            temp=max(nums[lp], nums[ptr], nums[rp]);
            if(temp==nums[lp]){
                if(flag){
                    flag=false;
                }else{
                    res+=temp;
                    flag=true;
                }
            }else if(temp==nums[ptr]){
                res+=temp;
                flag=true;
                lp++;
                ptr++;
                rp++;
            }else{
                if(flag){
                    flag=false;
                }else{
                    res+=nums[lp];
                    flag=true;
                }
            }
            lp++;
            ptr++;
            rp++;
        }

        if(!flag){
            if(nums[lp]>nums[ptr]){
                res+=nums[lp];
            }else{
                res+=nums[ptr];
            }
        }else{
            res+=nums[ptr];
        }

        return res;
    }

    private int max(int a, int b, int c){
        int res=a>b?a:b;
        res=res>c?res:c;

        return res;
    }
}

        思路---动态规划:

                动态规划,其本质是需要解决的问题A,A问题的解依赖于A的子问题B,B的问题又依赖于B的子问题C……

                有没有发现,其思维和递归类似,那为什么不叫递归呢?

                我的理解是,递归只是一种结构,动态规划是一种算法思想,动态规划融合了很多东西,不完全是递归,只能说代码上有重合。【而且动态规划能用迭代写出来(虽然递归的本质也是迭代,但是有一些问题比如回溯你很难用迭代写出来,很奇怪,但就是这样,你甚至可以把递归看成新的数据结构)】

                对于8个房间,我们想求其偷盗最优解。(从下标0开始计算,即0、1、2……6、7)

                根据题意,下标7的房间偷盗最优解,与下标6、5房间的偷盗最优解相关。

                如果下标5房间最优解+下标7的money,比下标6房间最优解大,那么,下标7的最优解明显是5优+7,否则是6优。

                这个式子叫做状态转移方程,看着就烦,虽然做多了隐隐约约也能明白,但是为什么会这样,还是多少有点疑惑。

                别急,解题难点分析告诉你答案。

解题难点分析:

        如上,我们从朴素的角度,思考状态转移方程的问题:

        难点1:从尾看到头,太难理解

        现象1:

                如果用朴素的组合思想。

                对于3家房间,我们知道,有2种组合(偷1、3,或偷2)

                也就是说,总的解决方案,有2种。

                我们选择这两种中最优。

                对于4家房间,有3种。(偷1、3, 偷1、4, 偷2、4)

                5家方案,有4种(偷1、3、5,偷1、4,偷2、4,偷2、5)

                观察发现,对于x家方案,无论如何,其必然与x-2或者x-1有关。

                并且,只要有x-2,就一定有x;

                只要有x-1,就一定没有x和x-2。

        现象2:

                对于5家房间,我们舍弃第5个房间,从4开始看。

                那么,有3种方案(偷1、3,偷1、4,偷2、4)

                这三种方案,组合形成最优方案。

                如果舍弃4、5,从3开始理解,也一样。

                3和4的比较可以推出5,4和5的比较可以推出6……

                也就是说,从x开始,往前看,其与x+1的比较,必能得到x+2的最优解。

代码:

class Solution {
    public int rob(int[] nums) {
        int p, j, res=0;
        // 由于x只与x-1、x-2有关,就可以一边得到子问题最优化,一边步进,舍弃无用解

        // 特殊情况
        if(nums.length==1){
            return nums[0];
        }else if(nums.length==2){
            return Math.max(nums[0], nums[1]);
        }

        // 最小的两个子问题
        p=nums[0];
        j=nums[0]>nums[1]?nums[0]:nums[1];

        // 求子问题解,并得到主问题解
        int len=nums.length;
        for(int i=2; i<nums.length; i++){
            res=Math.max(nums[i]+p, j);
            p=j;
            j=res;
        }

        return res;
    }
}

以上内容即我想分享的关于力扣热题21的一些知识。

        我是蚊子码农,如有补充,欢迎在评论区留言。个人也是初学者,知识体系可能没有那么完善,希望各位多多指正,谢谢大家。

  • 20
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值