做一个聪明的小偷——打家劫舍(一维动态规划)

不怕小偷有文化,就怕小偷会规划。
今天来看一道一维动态规划的例题:打家劫舍
首先来尝试一下利用递归法求解
先来考虑一下是否可以用递归法求解
那么我们需要考虑这几个条件是否满足:

1.是否可以分解为子问题
2.是否满足最优子结构
3.是否存在递归基

是否可以分解为子问题

假设我们现在偷的房子集为[2,7,9,3,1],我们可以假设小偷是从左偷到右的,而当我们偷到最右边也就是财富值为1的房子时,就代表所有的房子全部偷完了。而当我们偷到最后一个房子的时候,我们可以产生两个选择:偷或者不偷。显然,无论是我们做了哪一个选择,该问题都能被分解。

偷,则分解为偷[2, 7, 9]和1
不偷,则分解为偷[2,7,9,3]

是否满足最优子结构

何谓最优子结构?
即由子问题的最优解可以得出该问题的最优解。
显然,无论我们分解成何种情况,最大的金额总是能由子问题的最大金额得出,故满足最优子结构。

是否存在递归基

考虑最基本的情况。

1.只有一个房子时:最大金额即为这个房子的财富值
2.当有两个房子的时候,来到最后一个房子也就是第二个房子,首先考虑是否可以分解为上面两种情况。显然,当选择偷的时候,由于该房子前面只有一个房子,故不能分解为上面两种分解情况中的第一种情况。所以有两个房子的情况也为基本的情况,而最大金额则为两个房子财富值中的最大值。
3.当有三个房子的时候,显然可以分为:偷第三个房子——偷第一个房子和第三个房子,不偷第三个房子——只偷到第二个房子,放弃第三个房子。

由此得出两种基本情况:只有一个或者两个房子。
这便是我们的递归基。

递归实现

既然以上三个条件均满足,那么该题可用递归法解。
考虑到题目函数的参数的特殊性,如果不借助辅助函数,递归法可以用Python来写:

def rob(self, nums):
	return max(nums) if len(nums) < 3 else max(nums[-1] + self.rob(nums[0 : -2]), self.rob(nums[0 : -1]))

而借助辅助函数(表示当前偷到哪一间房为止),则也可用c++写:

int rob(vector<int>& nums) {
	return helper(nums, nums.size());
}

int helper(vector<int>& nums, int n){
	return n == 1 ? nums[0] : n == 2 ? max(nums[0], nums[1]) : max(nums[n - 1] + helper(nums, n - 2), helper(nums, n - 1));
}

当然,这种方法肯定会超时,我们可以改为带备忘录的递归。

int rob(vector<int>& nums) {
	return robCur(nums, nums.size());
}

int robCur(vector<int>& nums, int n) {	//当前偷到哪一间为止
	vector<int> m(nums.size() + 1, -1);
	return helper(nums, m, n);
}

int helper(vector<int>& nums, vector<int>& m, int n) {
	return m[n] != -1 ? m[n] : n == 1 ? m[1] = nums[0] : n == 2 ? m[2] = max(nums[0], nums[1]) : m[n] = max(nums[n - 1] + helper(nums, m, n - 2), helper(nums, m, n - 1));
}

动态规划实现

纯递归法之所以超时,是因为存在大量重复子问题,那么我们就可以用动态规划来求解。
首先确认状态和状态转移方程:

状态:dp[n]为偷到第n间房子为止。
状态转移方程:dp[n] = max(nums[n - 1] + dp[n - 2], dp[n - 1])

具体实现如下:

int rob(vector<int>& nums) {
	if(nums.empty()) return 0;
    int n = nums.size();
    vector<int> dp(n + 1, 0);
    dp[1] = nums[0];	//原本是需要将dp[2]赋初值的,这里取巧,因为dp[0]的存在就不用赋初值了
    for(int i = 2; i <= n; ++i){
    	dp[i] = max(dp[i - 1], dp[i - 2] + nums[i - 1]);
    }
    return dp[n];
}

当然也可以进一步优化空间:

int rob(vector<int>& nums) {
	if(nums.empty()) return 0;
    int n = nums.size();
    if(n == 1) return nums[0];
    int pre1 = 0, pre2 = 0, cur;
    for(int i = 1; i <= n; ++i){
    	cur = max(pre1, pre2 + nums[i - 1]);
        pre2 = pre1;
        pre1 = cur;
    }
    return cur;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值