力扣198——打家劫舍

这次准备连讲三道题,这道题就是最基础的,利用动态规划可以解决。

原题

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

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

示例 :

输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4 。

原题url:https://leetcode-cn.com/problems/house-robber/

解题

动态规划-自顶向下

动态规划大家应该很容易就想到,如果偷了当前的金钱,下一家就不能偷,如果不偷当前的金钱,可以考虑下一家。比如:

小偷到了第3家,他有两个选择:不偷第3家之后去第4家、偷完第3家之后去第5家。这时他需要比较的是:

  • 从第4家开始能偷到的最多金钱
  • 第3家的金钱加上从第5家开始能偷到的最多金钱
    上面两者谁更多,就选择怎么做。

那主要目的就是要求出从当前到结尾可以偷到的最大值,为了不重复计算,可以利用一个数组记录中间结果。

接下来看看代码:

class Solution {
    public int rob(int[] nums) {
        if (nums.length == 0) {
            return 0;
        }

        // 存储中间结果
        int[] result = new int[nums.length];
        Arrays.fill(result, -1);
        // 动态规划,利用中间结果,寻找最大值
        dp(0, nums, result);
        return result[0];
    }

    public int dp(int start, int[] nums, int[] result) {
        if (start >= nums.length) {
            return 0;
        }

        if (result[start] != -1) {
            return result[start];
        }

        result[start] = Math.max(
            // 选择偷当前的家
            nums[start] + dp(start + 2, nums, result),
            // 选择不偷当前的家
            dp(start + 1, nums, result)
        );

        return result[start];
    }
}

提交OK。

动态规划-自底向上

上面的写法其实是从头向尾考虑,写法上是递归。那么如果想不用递归呢?毕竟递归也是有缺陷的,如果次数过多,总调用栈就会很长。那我们来改造一下。

如果我们是从尾向头考虑呢?也就是从最后一家开始,选择偷或者不偷,最终到第一家。思想上还是很好理解的,和上面差不多,让我们看看代码:

class Solution {
    public int rob(int[] nums) {
        if (nums.length == 0) {
            return 0;
        }

        // 存储中间结果
        int[] result = new int[nums.length + 2];
        // 动态规划,利用中间结果,寻找最大值
        for (int i = nums.length - 1; i >= 0; i--) {
            result[i] = Math.max(
                // 当前不偷
                result[i + 1],
                // 当前偷
                nums[i] + result[i + 2]
            );
        }
        return result[0];
    }
}

提交也是OK的。

空间上的优化

我们仔细观察一下上面的写法,其实每次你利用到的中间状态只有两个,下一个位置和再下一个位置。那么此时我们也可以用三个变量来存储就够了,两个存储之后的值,还有一个存储当前的值。

让我们来看看代码:

class Solution {
    public int rob(int[] nums) {
        if (nums.length == 0) {
            return 0;
        }

        // 存储当前位置,下一个位置,和再下一个位置的结果
        int current = 0;
        int next_1 = 0;
        int next_2 = 0;
        // 动态规划,利用中间结果,寻找最大值
        for (int i = nums.length - 1; i >= 0; i--) {
            current = Math.max(
                // 当前不偷
                next_1,
                // 当前偷
                nums[i] + next_2
            );
            next_2 = next_1;
            next_1 = current;
        }
        return current;
    }
}

总结

以上就是这道题目我的解答过程了,不知道大家是否理解了。这道题主要利用动态规划就可以解决,可以改写递归,优化空间复杂度。

有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。

https://death00.github.io/

公众号:健程之道

posted on 2020-01-20 21:12  jianjianqq  阅读(248)  评论(0编辑  收藏  举报