
🔥小叶-duck:个人主页
❄️个人专栏:《Data-Structure-Learning》《C++入门到进阶&自我学习过程记录》
《算法题讲解指南》--优选算法
《算法题讲解指南》--递归、搜索与回溯算法
《算法题讲解指南》--动态规划算法
✨未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游
目录
11.按摩师
题目链接:
题目描述:

题目示例:

解法(动态规划):
算法思路:
1.状态表示:
对于简单的线性dp,我们可以用「经验 +题目要求」来定义状态表示:
i.以某个位置为结尾,巴拉巴拉;
ii.以某个位置为起点,巴拉巴拉。
这里我们选择比较常用的方式,以某个位置为结尾,结合题目要求,定义一个状态表示:
dp[i]表示:选择到i位置时,此时的最长预约时长。
但是我们这个题在i位置的时候,会面临「选择」或者「不选择」两种抉择,所依赖的状态需要细分:
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[i]不选,那么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]) 。
C++算法代码:
cpp
class Solution {
public:
int massage(vector<int>& nums)
{
if(nums.size() == 0)
{
return 0;
}
//vector<int> dp(nums.size());
vector<int> f(nums.size());
vector<int> g(nums.size());
//dp[i]表示第i个位置所预约的最长时间,通过f和g来表示,可以不创建
//f[i]表示第i个位置选择预约时的预约最长时间;
//g[i]表示第i个位置不选择预约时的预约最长时间
f[0] = nums[0]; //g[0] = 0,创建数组时g[0]就是0
for(int i = 1; i < nums.size(); i++)
{
f[i] = g[i - 1] + nums[i];
g[i]= max(f[i - 1], g[i - 1]);
//dp[i] = max(f[i], g[i]);
//由于f[i]不知道,所以只能由前一个位置得到:
//因为f[i]表示第i个位置选择预约,则i-1位置一定不选择预约->g[i-1]
//所以f[i] = g[i - 1] + nums[i]
//由于g[i]不知道,所以只能由前一个位置得到:
//因为g[i]表示第i个位置不选择预约,则i-1位置又会有两种情况:
//如果第i-1位置选择预约->f[i-1],则g[i]=f[i-1]
//如果第i-1位置不选择预约->g[i-1],则g[i]=g[i-1]
//所以我们要得到预约的最长时间->g[i]=max(f[i - 1], g[i - 1])
}
//return dp[nums.size() - 1];
return max(f[nums.size() - 1], g[nums.size() - 1]);
}
};
算法总结及流程解析:


前置任务:打家劫舍
题目链接:
题目描述:

题目示例:

C++算法代码:
cpp
class Solution {
public:
int rob(vector<int>& nums)
{
int n = nums.size();
vector<int> f(n);//f[i]表示第i个位置选择偷时,当前偷到的最大金额
vector<int> g(n);//g[i]表示第i个位置选择不偷,当前偷到的最大金额
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]);
}
};
12.打家劫舍II
题目链接:
题目描述:

题目示例:

解法(动态规划):
算法思路:
这一个问题是「打家劫舍I」问题的变形。
上一个问题是一个「单排」的模式,这一个问题是一个「环形」的模式,也就是首尾是相连的。但是我们可以将「环形」问题转化为「两个单排」问题:
a.偷第一个房屋时的最大金额x ,此时不能偷最后一个房子,因此就是偷[0,n -2]区间的房子;
b.不偷第一个房屋时的最大金额 y ,此时可以偷最后一个房子,因此就是偷[1,n-1]区
间的房子;
两种情况下的「最大值」,就是最终的结果。
因此,问题就转化成求「两次单排结果的最大值」。
C++算法代码:
cpp
class Solution {
public:
//环形打家劫舍
int rob(vector<int>& nums)
{
int n = nums.size();
return max(nums[0] + Rob(nums, 2, n - 2), Rob(nums, 1, n - 1));
//当选择偷第0个位置,则第1个位置和n-1位置都不能偷
//则(2, n - 2)区间满足线性打家劫舍的逻辑
//当不选择偷第0个位置,则(1, n - 1)区间满足线性打家劫舍的逻辑
}
//线性打家劫舍
int Rob(vector<int>& nums, int left, int right)
{
if(left > right)
{
return 0;
}
int n = right - left + 1;
vector<int> f(n);//f[i]表示第i个位置选择偷时,当前偷到的最大金额
vector<int> g(n);//g[i]表示第i个位置选择不偷,当前偷到的最大金额
f[0] = nums[left]; //一定要注意nums下标的映射关系
for(int i = 1; i < n; i++)
{
f[i] = g[i - 1] + nums[i + left];//这里也是要注意nums的对应位置
g[i] = max(f[i - 1], g[i - 1]);
}
return max(f[n - 1], g[n - 1]);
}
};
算法总结及流程解析:


结束语
到此,11.按摩师,12.打家劫舍II 这两道算法题就讲解完了。**按摩师问题:通过定义f[i](选择i位置)和g[i](不选i位置)两个状态表示,建立状态转移方程并初始化,最终返回最大值;打家劫舍II:则将环形结构转化为两个线性问题:偷第一个房屋(不偷最后一个)和不偷第一个房屋(可偷最后一个),分别求解后取最大值。**希望大家能有所收获!