【动态规划】打家劫舍系列

打家劫舍I

题目描述
力扣 【打家劫舍I】

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

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

示例 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、首先判断是否存在房屋,若没有房屋则直接返回0
2、当只存在一间房屋时,小偷所偷的金额只有这一间房屋的金额
3、当房屋数量为两间时,小偷会选择其中较大金额的房屋进行偷窃
4、当房屋数量大于两间时,则会出现以下情况:
(1)假设偷窃第i间房,则不能偷窃第i-1间房,偷窃的总金额为前i-2将房的最高金额与第i间房的金额之和;
(2)假设不偷第i间房,则偷窃的总金额为前i-1将房的最高金额

综上所述:
初始条件:dp[0] = nums[0],dp[1] = max(nums[0],nums[1])
状态转移方程:dp[i] = max(nums[i-1],nums[i-1]+nums[i])
返回值:dp[nums.size()-1]

复杂度分析
时间复杂度:O(n),其中n=nums.size()-1;
空间复杂度:O(1)

代码实现

//https://leetcode-cn.com/problems/house-robber/

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int rob(vector<int>& nums)
{
	if (nums.empty())
	{
		return 0;
	}
	if (nums.size() == 1)
	{
		return nums[0];
	}
	vector<int>dp(nums.size(), 0);
	dp[0] = nums[0];
	dp[1] = max(nums[0], nums[1]);
	for (int i = 2; i < nums.size(); i++)
	{
		dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);
	}
	return dp[nums.size() - 1];
}

int main()
{
	vector<int> nums = {0};
	cout << rob(nums) << endl;
	system("pause");
	return 0;
}

打家劫舍II


题目描述
力扣 【打家劫舍II】

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

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

示例 1:
输入: [2,3,2]
输出: 3
解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。

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

题解
本题是【打家劫舍I】的拓展版本,本题与【打家劫舍I】的区别在于:【打家劫舍I】的房屋排列是线性的,而本题的房屋排列是环形的,所以偷窃第一个房屋后就不能偷窃最后一个房屋,而不偷窃第一个房屋,就可以偷窃最后一个房屋,所以可以将本题的环形房屋拆解成两个线性的房屋排列,如下:
第一个:0,1,2,3……n-2
第二个:1,2,3,4……n-1
将环形房屋拆解成两个线性排列的房屋后,可以直接使用【打家劫舍】的解题思路即可。

复杂度分析
时间复杂度O(n)
空间复杂度O(1)

代码实现

//https://leetcode-cn.com/problems/house-robber-ii/

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int rob(vector<int>& nums)
{
	if (nums.empty())
	{
		return 0;
	}
	if (nums.size() == 1)
	{
		return nums[0];
	}
	else if (nums.size() == 2)
	{
		return max(nums[0], nums[1]);
	}

	vector<int> dp1(nums.size() - 1, 0);
	dp1[0] = nums[0];
	dp1[1] = max(nums[0], nums[1]);
	for (int i = 2; i < nums.size() - 1; i++)
	{
		dp1[i] = max(dp1[i - 1], dp1[i - 2] + nums[i]);
	}

	vector<int> dp2(nums.size(), 0);
	dp2[0] = 0;
	dp2[1] = nums[1];
	for (int i = 2; i < nums.size(); i++)
	{
		dp2[i] = max(dp2[i - 1], dp2[i - 2] + nums[i]);
	}
	
	return max(dp1[nums.size() - 2], dp2[nums.size() - 1]);
}
int main()
{
	vector<int> nums = { 0,0};
	cout << rob(nums) <<endl;
	system("pause");
	return 0;
}

打家劫舍III


题目描述
力扣 【打家劫舍III】

在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。

计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。

示例 1:
输入: [3,2,3,null,3,null,1]

 3
/ \
2   3
\   \ 
 3   1

输出: 7
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.

示例 2:
输入: [3,4,5,1,3,null,1]

 3
/ \
4   5
/ \   \ 
1   3   1

输出: 9
解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.

题解
这个题将【打家劫舍I】拓展为树形结构,和打家劫舍I一样,不能选择相邻的两个节点。所以对于一个子树来说,有两种情况:

  • 包含当前根节点
  • 不包含当前根节点

情况1:包含根节点
由于包含了根节点,所以不能选择左右儿子节点,这种情况的最大值为:当前节点 + 左儿子情况2 + 右二子情况2

情况2:不包含根节点
这种情况,可以选择左右儿子节点,所以有四种可能:

左儿子情况1 + 右儿子情况1
左儿子情况1 + 右儿子情况2
左儿子情况2 + 右儿子情况1
左儿子情况2 + 右儿子情况2
综合来说就是,max(左儿子情况1, 左儿子情况2) + max(右儿子情况1, 右儿子情况2)。

综合两种情况,深度优先,从叶子节点遍历到根节点即可。
由于只有两种情况,可以选择使用pair来存储这两种情况的数据值。
pair的含义:<情况1包含当前节点的最大值,情况2不包含当前节点的最大值>

代码实现

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    pair<int, int> dfs(TreeNode *root) 
    {
        if (root == nullptr)
        {
            return { 0, 0 };
        }
        
        auto left_pair = dfs(root->left);
        auto right_pair = dfs(root->right);
        return { root->val + left_pair.second + right_pair.second, 
            max(left_pair.first, left_pair.second) + max(right_pair.first, right_pair.second)
        };
};
    
    int rob(TreeNode* root) 
    {
        auto p = dfs(root);
        return max(p.first, p.second);
    }
};

参考leetcode题解:题解


删除与获得点数


题目描述
力扣 【删除与获得点数】

给定一个整数数组 nums ,你可以对它进行一些操作。

每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数。之后,你必须删除每个等于 nums[i] - 1 或 nums[i] + 1 的元素。

开始你拥有 0 个点数。返回你能通过这些操作获得的最大点数。

示例 1:
输入: nums = [3, 4, 2]
输出: 6
解释:
删除 4 来获得 4 个点数,因此 3 也被删除。
之后,删除 2 来获得 2 个点数。总共获得 6 个点数。

示例 2:
输入: nums = [2, 2, 3, 3, 3, 4]
输出: 9
解释:
删除 3 来获得 3 个点数,接着要删除两个 2 和 4 。
之后,再次删除 3 获得 3 个点数,再次删除 3 获得 3 个点数。
总共获得 9 个点数。

题解
这个题是【打家劫舍】的变形题,在做这个题之前,建议先做【打家劫舍】系列。
这个题与【打家劫舍】的区别在于:本题是删除nums[i]-1和nums[i]+1的数,而【打家劫舍】是排除nums[i-1]和nums[i+1],他们排除的元素所处位置不同,但是本题可以转化为打家劫舍系列的问题,将输入的数组元素转化为打家劫舍系列的数组
如:nums = [2,2,3,3,3,4]可以转化为 tmp = [0,0,2,3,1]
转化后的数组为每个元素对应位置的个数,经过转化,即可转化为【打家劫舍系列问题】

代码实现

class Solution {
public:
    int deleteAndEarn(vector<int>& nums)
    {
        if(nums.empty())
        {
            return 0;
        }
        if(nums.size() == 1)
        {
            return nums[0];
        }

        vector<int> tmp(20001,0);
        for(auto n:nums)
        {
            tmp[n] += n;
        }

        vector<int> dp(20001,0);
        dp[0] = tmp[0];
        dp[1] = max(tmp[0],tmp[1]);
        for(int i = 2; i < 20001; i++)
        {
            dp[i] = max(dp[i-1],dp[i-2]+tmp[i]);
        }
        return dp[20000];
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值