顾得泉:个人主页
个人专栏:《Linux操作系统》 《C/C++》 《LeedCode刷题》
键盘敲烂,年薪百万!
一、打家劫舍
题目链接:打家劫舍
题目描述
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 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 <= nums.length <= 100
0 <= nums[i] <= 400
解法
1.状态表示:
对于简单的线性dp,我们可以用「经验+题目要求」来定义状态表示:
i.以某个位置为结尾,巴拉巴拉;
ii.以某个位置为起点,巴拉巴拉。
这里我们选择比较常用的方式,以某个位置为结尾,结合题目要求,定义一个状态表示:
dp[i]表示:选择到i位置时,此时的最高金额。
但是我们这个题在1位置的时候,会面临「选择」或者「不选择」两种抉择,所依赖的状态需要细分:
f[i]表示:选择到i位置时,nums[i]必选,此时的最高金额;
g[i]表示:选择到i位置时,nums[i]不选,此时的最高金额。
2.状态转移方程:
因为状态表示定义了两个,因此我们的状态转移方程也要分析两个:
对于f[i]:
如果nums[i]必选,那么我们仅需知道[i - 1]位置在不选的情况下的最高金额,然后加上nums[i]即可,因此f[i] = g[i - 1]+ nums[i]
对于g[i]:
如果nums[j]不选,那么[i - 1]位置上选或者不选都可以。因此,我们需要知道[i- 1]位宣上选或者不选两种情况下的最长时长,因此 g[i] = max (f[i - 1],g[i- 1])
3.初始化:
这道题的初始化比较简单,因此无需加辅助节点,仅需初始化 f[0] = nums[0],g[0] = 0即可
4.填表顺序:
根据「状态转移方程」得「从左往右,两个表一起填」
5.返回值
根据「状态表示」,应该返回max(f[n - 1],g[n - 1])
代码实现
class Solution {
public:
int rob(vector<int>& nums)
{
int n = nums.size();
if(n == 0) return nums[0];
vector<int> f(n);
auto g = f;
f[0] = nums[0];
for(int i = 1; i < n; i++)
{
f[i] = g[i-1] + nums[i];
g[i] = max(f[i-1], g[i-1]);
}
return max(f[n-1], g[n-1]);
}
};
二、打家劫舍Ⅱ
题目链接:打家劫舍 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 = [1,2,3] 输出:3
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 1000
解法
这一个问题是「打家劫舍」问题的变形。
上一个问题是一个「单排」的模式,这一个问题是一个「环形」的模式,也就是首尾是相连的。但是我们可以将「环形」问题转化为「两个单排」问题:
a.偷第一个房屋时的最大金额×,此时不能偷最后一个房子,因此就是偷[0,n - 2]区间的房子;
b.不偷第一个房屋时的最大金额y,此时可以偷最后一个房子,因此就是偷[1,n - 1]区间的房子;
两种情况下的「最大值」,就是最终的结果
因此,问题就转化成求「两次单排结果的最大值」
代码实现
class Solution {
public:
int rob(vector<int>& nums)
{
int n = nums.size();
return max(nums[0] + rob1(nums, 2, n - 2), rob1(nums, 1, n - 1));
}
int rob1(vector<int>& nums, int left, int right)
{
if(left > right) return 0;
int n = nums.size();
vector<int> f(n);
auto g = f;
f[left] = nums[left];
for(int i = left + 1; i <= right; i++)
{
f[i] = g[i-1] + nums[i];
g[i] = max(f[i-1], g[i-1]);
}
return max(f[right], g[right]);
}
};
三、删除并获得点数
题目链接:删除并获得点数
题目描述
给你一个整数数组 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 个点数。
提示:
1 <= nums.length <= 2 * 104
1 <= nums[i] <= 104
解法
其实这道题依旧是「打家劫舍」问题的变型。我们注意到题目描述,选择×数字的时候,×-1与x+1是不能被选择的。像不像「打家劫舍」问题中,选择i位置的金额之后,就不能选择i - 1位置以及i+1位置的金额呢~
因此,我们可以创建一个大小为10001(根据题目的数据范围)的hash 数组,将nums 数组中每一个元素×,累加到,hash 数组下标为x的位置处,然后在hash数组上来一次「打家劫舍」即可
代码实现
class Solution {
public:
int deleteAndEarn(vector<int>& nums)
{
const int n = 10001;
int arr[n] = {0};
for(auto x : nums) arr[x] += x;
vector<int> f(n);
auto g = f;
for(int i = 1; i < n; i++)
{
f[i] = g[i-1] + arr[i];
g[i] = max(f[i-1], g[i-1]);
}
return max(f[n-1], g[n-1]);
}
};
结语:今日的刷题分享到这里就结束了,希望本篇文章的分享会对大家的学习带来些许帮助,如果大家有什么问题,欢迎大家在评论区留言~~~