LeetCode 198. House Robber && LeetCode 213. House Robber II

题目

You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security system connected and it will automatically contact the police if two adjacent houses were broken into on the same night.

Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.

Example 1:

Input: [1,2,3,1]
Output: 4
Explanation: Rob house 1 (money = 1) and then rob house 3 (money = 3).
             Total amount you can rob = 1 + 3 = 4.

Example 2:

Input: [2,7,9,3,1]
Output: 12
Explanation: Rob house 1 (money = 2), rob house 3 (money = 9) and rob house 5 (money = 1).
             Total amount you can rob = 2 + 9 + 1 = 12.

这道题也是之前在算法作业水过的一道题,应该算是一道非常典型的动态规划。题意大概就是给定一个数组,挑选不相邻的数字相加,求最大。那么对数组中的每一个元素,都有取它和不取它两种操作,如果取它,那么顶多就只能rob它前两个元素那么多,如果不取,就可以rob到它前一个元素能rob到的钱。因此,递推关系大概是rob(i) = Math.max( rob(i - 2) + currentHouseValue, rob(i - 1) )

知道递推关系就可以很方便地写出代码了。这道题一共有四种实现方式,我自己觉得比较straight-forward的方法是用一个数组存放每个元素位置对应的最大值,结果取新数组的最后一个值。时间复杂度O(n),4ms,56.92%,空间复杂度O(n),8.6M,84.91%:

class Solution {
public:
    int rob(vector<int>& nums) {
        if (nums.size() == 0) {
            return 0;
        }
        if (nums.size() == 1) {
            return nums[0];
        }
        vector<int> amount;
        amount.push_back(nums[0]);
        amount.push_back(max(nums[0], nums[1]));
        for (int i = 2; i < nums.size(); i++) {
            amount.push_back(max(amount[i - 2] + nums[i], amount[i - 1]));
        }
        return amount.back();
    }
};

2020.10.5 Java:

Runtime: 0 ms, faster than 100.00% of Java online submissions for House Robber.

Memory Usage: 36.5 MB, less than 92.14% of Java online submissions for House Robber.

class Solution {
    public int rob(int[] nums) {
        int len = nums.length;
        if (len == 0) {
            return 0;
        } else if (len == 1) {
            return nums[0];
        }
        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];
    }
}

注意到上面这个写法,因为是严格按照递推式写的,于是需要在前面判断到至少有一个元素的情况,写起来有点冗余。看了一下discussion里其他人写的,发现可以不一定要新数组与原数组一样大,可以先在前面push一个0进去,这样就不需要再判断数组中只有一个元素的情况,最后取最后一个元素也是一样的。

迭代的写法还有另外一种更加简洁的,因为递推式里只用到了i-1和i-2两个结果,因此只需要定义两个变量存储这两个结果即可。代码写起来是真的非常简单,时间复杂度O(n),4ms,56.37%,空间复杂度O(n),8.5M,98.11%:

class Solution {
public:
    int rob(vector<int>& nums) {
        int prev1 = 0;
        int prev2 = 0;
        for (int i = 0; i < nums.size(); i++) {
            int temp = max(prev2 + nums[i], prev1);
            prev2 = prev1;
            prev1 = temp;
        }
        return prev1;
    }
};

Java:

class Solution {
    public int rob(int[] nums) {
        int len = nums.length;
        int prev1 = 0;
        int prev2 = 0;
        for (int i = 0; i < len; i++) {
            int result = Math.max(prev1 + nums[i], prev2);
            prev1 = prev2;
            prev2 = result;
        }
        return prev2;
    }
}

 


接下来要用递归的方式写一下,虽然空间效率不如直接迭代,但是为了让自己熟练一下递归,就还是写一下吧。写了也更深刻地意识到了自己的递归还不是很熟,虽然大体上的思路比较正确,但是还是会卡壳,最后是看了discussion的解法才慢慢理解。递归函数包括原始的数组和一个指示长度的size,由于要递归到前面两位,所以在处理最开始的第一个和第二个元素的时候就会遇到size为负的情况,因此在递归基就要判断是否小于0,小于0的都return 0,而当等于0的时候(也就是数组里只有一个元素的时候)就可以按照递推式正常判断了。(下面这种解法在rob函数里写的是nums.size() - 1,所以底下取nums是应该取size,并且递归基是小于0,如果上面不-1的话,底下就要-1并把递归基的判断条件变成小于等于0,其他的不影响)

然后submit的时候发现居然TLE了……看来还真是不能轻易递归啊:

class Solution {
public:
    int rob(vector<int>& nums) {
        return amount(nums, nums.size() - 1);
    }
    
    int amount(vector<int>& nums, int size) {
        if (size < 0) {
            return 0;
        }
        return max(amount(nums, size - 2) + nums[size], amount(nums, size - 1));
    }
};

因为普通的递归会重复计算同一个amount好几次,因此可以通过把之前计算过的amount给记下来,后面用到时直接取就可以了,这样可以减少计算次数。改进版的递归代码如下:

class Solution {
public:
    int rob(vector<int>& nums) {
        vector<int> amounts;
        return amount(nums, nums.size() - 1, amounts);
    }
    
    int amount(vector<int>& nums, int size, vector<int>& amounts) {
        if (size < 0) {
            return 0;
        }
        if (amounts.size() > size) {
            return amounts[size];
        }
        int result = max(amount(nums, size - 2, amounts) + nums[size], amount(nums, size - 1, amounts));
        amounts.push_back(result);
        return result;
    }
};

改进版的递归终于可以拥有姓名,运行时间4ms,56.37%,内存8.9M,5.66%……

2020.10.5 Java:还是不太会写递归+memo,刚开始没想着用index作为参数,还在递归函数里面slice数组……看了解答才意识到。

Runtime: 0 ms, faster than 100.00% of Java online submissions for House Robber.

Memory Usage: 35.9 MB, less than 99.96% of Java online submissions for House Robber.

class Solution {
    public int rob(int[] nums) {
        int[] memo = new int[nums.length];
        Arrays.fill(memo, -1);
        return recursion(nums, nums.length - 1, memo);
    }
    
    private int recursion(int[] nums, int i, int[] memo) {
        if (i < 0) {
            return 0;
        }
        if (memo[i] < 0) {
            memo[i] = Math.max(recursion(nums, i - 1, memo), recursion(nums, i - 2, memo) + nums[i]);
        }
        return memo[i];
    }
}

213 House Robber II

这道题和之前那道的唯一区别就是这道题的房子是头尾相连的,也就是rob了第一个就不能rob最后一个。解题思路其实可以通过前面那题迁移过来。这道题相当于要么就是rob第一个到第n-1个,要么就是rob第二个到第n个。把之前那道题的函数改写成带开头结尾的,这道题调用一下比较一下return最大就完事了。

Runtime: 0 ms, faster than 100.00% of Java online submissions for House Robber II.

Memory Usage: 36.4 MB, less than 94.38% of Java online submissions for House Robber II.

class Solution {
    public int rob(int[] nums) {
        if (nums.length == 1) {
            return nums[0];
        }
        int rob1 = robFromRange(nums, 0, nums.length - 1);
        int rob2 = robFromRange(nums, 1, nums.length);
        return Math.max(rob1, rob2);
    }
    
    public int robFromRange(int[] nums, int start, int stop) {
        int prev1 = 0;
        int prev2 = 0;
        for (int i = start; i < stop; i++) {
            int result = Math.max(prev1 + nums[i], prev2);
            prev1 = prev2;
            prev2 = result;
        }
        return prev2;
    }
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值