zoukankan      html  css  js  c++  java
  • Medium | LeetCode 213. 打家劫舍 II | 动态规划

    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 = [0]
    输出:0
    

    提示:

    • 1 <= nums.length <= 100
    • 0 <= nums[i] <= 1000

    解题思路

    方法一: 动态规划

    Medium | LeetCode 198. 打家劫舍 | 动态规划 类似。不过加了一个条件, 前一道题是线型的, 这一道题是环形的。区别在于环形的最后一个数字和数组的第一个数字不能同时偷。

    直接分类讨论, 分两种情况讨论。

    第一种情况是第一个偷, 把第一个写死。
    第二种情况是第一个不偷, 把第一个写死。

    针对这两种情况, 使用与前面一道题的动态规划迭代方法。一直迭代到倒数第二个元素。

    在迭代倒数第一个元素, 也是返回结果时。有两种可能性, 从这两种可能性取最大值返回即可。

    最后一个不偷, 那么返回值就是上面的第一个偷, 第一个不偷 这两种策略一直迭代到倒数第二个元素的值的较大者。
    最后一个偷, 那么返回值是 第一个不偷+最后一个偷

    对以上两种情况取最大值返回即可。

    public int rob(int[] nums) {
        int len = nums.length;
        if (len == 0) {
            return 0;
        }
        if (len == 1) {
            return nums[0];
        }
        if (len == 2) {
            return Math.max(nums[0], nums[1]);
        }
        // 设置两个数组, 分别表示第一个偷与不偷, 在两个数组里, 第一个值直接写死
        int[] firstRub = new int[len];
        firstRub[0] = nums[0];firstRub[1] = nums[0];
        int[] firstUnRub = new int[len];
        firstUnRub[1] = nums[1];
        // 动态规划向前迭代
        for (int i = 2; i < len - 1; i++) {
            firstRub[i] = Math.max(firstRub[i-1], firstRub[i-2] + nums[i]);
            firstUnRub[i] = Math.max(firstUnRub[i-1], firstUnRub[i-2] + nums[i]);
        }
        return Math.max(
                Math.max(firstRub[len-2], firstUnRub[len-2]), // 最后一个不偷
                firstUnRub[len-3] + nums[len-1]); // 最后一个偷
    }
    

    方法二: 动态规划 + 滚动数组

    方法一借助了两个辅助DP数组, 用于标识两种策略下偷到第i个物品的总价值。时间复杂度是O(N)。这其实可以通过滚动来实现。

    下面的代码的空间复杂度是O(1)

    public int rob(int[] nums) {
        if (nums.length == 0) {
            return 0;
        }
        if (nums.length == 1) {
            return nums[0];
        }
        if (nums.length == 2) {
            return Math.max(nums[0], nums[1]);
        }
        // 设置两个数组, 分别表示第一个偷与不偷, 在两个数组里, 第一个值直接写死
        int grandPre1 = nums[0], pre1 = nums[0];
        int grandPre2 = 0, pre2 = nums[1];
        int cur1 = 0, cur2 = 0;
        // 动态规划向前迭代
        for (int i = 2; i < nums.length - 1; i++) {
            cur1 = Math.max(pre1, grandPre1 + nums[i]);
            grandPre1 = pre1;
            pre1 = cur1;
            cur2 = Math.max(pre2, grandPre2 + nums[i]);
            grandPre2 = pre2;
            pre2 = cur2;
        }
        return Math.max(
                Math.max(pre1, pre2), // 最后一个不偷
                grandPre2 + nums[nums.length - 1] // 最后一个偷
        );
    }
    

    除此之外, 还有一种更加简洁的写法, 如下

    直接按照第一个是否偷的原则, 讲一个数组拆分成两份。分别在两个数组内求结果返回其中的较大者。

    public int rob(int[] nums) {
        if(nums.length == 0) return 0;
        if(nums.length == 1) return nums[0];
        return Math.max(myRob(Arrays.copyOfRange(nums, 0, nums.length - 1)), 
                        myRob(Arrays.copyOfRange(nums, 1, nums.length)));
    }
    private int myRob(int[] nums) {
        int pre = 0, cur = 0, tmp;
        for(int num : nums) {
            tmp = cur;
            cur = Math.max(pre + num, cur);
            pre = tmp;
        }
        return cur;
    }
    
  • 相关阅读:
    设计模式03-工厂方法
    设计模式02-抽象工厂
    设计模式01-什么是设计模式
    工作流activiti-03数据查询(流程定义 流程实例 代办任务) 以及个人小练习
    工作流activiti-02事物控制、流程引擎创建
    工作流activiti-01个人小结
    jQuery.extend 函数详解
    hibernate框架学习之数据查询(QBC)
    hibernate框架学习之多表查询helloworld
    hibernate框架学习之数据查询(HQL)helloworld
  • 原文地址:https://www.cnblogs.com/chenrj97/p/14631891.html
Copyright © 2011-2022 走看看