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

题目示例:

解法(动态规划):
算法思路:
其实这道题依旧是「打家劫舍」问题的变型。
我们注意到题目描述,选择 x 数字的时候,x-1 与 x+1 是不能被选择的。像不像「打家劫舍」问题中,选择 i 位置的金额之后,就不能选择 i - 1 位置以及 i + 1 位置的金额呢?
因此,我们可以创建一个大小为 10001 (根据题目的数据范围)的 hash 数组,将 nums 数组中每一个元素 x,累加到 hash 数组下标为 x 的位置处,然后在 hash 数组上来一次「打家劫舍」即可。
C++算法代码:
cpp
class Solution {
public:
int deleteAndEarn(vector<int>& nums)
{
//统计每个数字出现的次数,放到一个数组中
int arr[10001] = { 0 };
for(int i = 0; i < nums.size(); i++)
{
arr[nums[i]]++;
}
vector<int> f(10001);//f[i]表示在arr中第i个位置选择删除并获得点数,此时获得的最大点数
vector<int> g(10001);//g[i]表示在arr中第i个位置不选择删除并获得点数,此时获得的最大点数
f[1] = arr[1] * 1;
for(int i = 2; i < 10001; i++)
{
f[i] = g[i - 1] + arr[i] * i;
g[i] = max(f[i - 1], g[i - 1]);
}
return max(f[10000], g[10000]);
}
};
算法总结及流程解析:


14.粉刷房子
题目链接:
题目描述:

题目示例:

解法(动态规划):
算法思路:
1.状态表示:
对于线性dp,我们可以用「经验+题目要求」来定义状态表示:
i.以某个位置为结尾,巴拉巴拉;
ii.以某个位置为起点,巴拉巴拉。
这里我们选择比较常用的方式,以某个位置为结尾,结合题目要求,定义一个状态表示:
但是我们这个题在i位置的时候,会面临「红」「蓝」「绿」三种抉择,所依赖的状态需要细
分:
dp[i][0]表示:粉刷到i位置的时候,最后一个位置粉刷上「红色」,此时的最小花费;
dp[i][1]表示:粉刷到i位置的时候,最后一个位置粉刷上「蓝色」,此时的最小花费;
dp[i][2]表示:粉刷到i位置的时候,最后一个位置粉刷上「绿色」,此时的最小花费。
2.状态转移方程:
因为状态表示定义了三个,因此我们的状态转移方程也要分析三个:
对于dp[i][0]:
如果第i个位置粉刷上「红色」,那么i-1位置上可以是「蓝色」或者「绿色」。因此我们需要知道粉刷到i-1位置上的时候,粉刷上「蓝色」或者「绿色」的最小花费,然后加上i位置的花费即可。于是状态转移方程为:dp[i][0]= min(dp[i - 1][1],dp[i- 1][2]) + costs[i - 1][0];
同理,我们可以推导出另外两个状态转移方程为:
dp[i][1] = min(dp[i - 1][0], dp[i - 1][2]) + costs[i - 1][1];
dp[i][2] = min(dp[i - 1][0], dp[i - 1][1]) + costs[i - 1][2]。
3.初始化:
可以在最前面加上一个「辅助结点」,帮助我们初始化。使用这种技巧要注意两个点:
i.辅助结点里面的值要「保证后续填表是正确的」;
ii.「下标的映射关系」。
在本题中,添加一个节点,并且初始化为0即可。
4.填表顺序
根据「状态转移方程」得「从左往右,三个表一起填」。
5.返回值
根据「状态表示」,应该返回最后一个位置粉刷上三种颜色情况下的最小值,因此需要返回:
min(dp[n][0], min(dp[n][1], dp[n][2])) 。
C++算法代码:
cpp
class Solution {
public:
int minCost(vector<vector<int>>& costs)
{
// //方法一:用三个一维dp数组实现动态规划:
// int n = costs.size();
// vector<int> red(n);
// vector<int> blue(n);
// vector<int> green(n);
// red[0] = costs[0][0];
// blue[0] = costs[0][1];
// green[0] = costs[0][2];
// for(int i = 1; i < n; i++)
// {
// red[i] = min(blue[i - 1], green[i - 1]) + costs[i][0];
// blue[i] = min(red[i - 1], green[i - 1]) + costs[i][1];
// green[i] = min(red[i - 1], blue[i - 1]) + costs[i][2];
// }
// return min(min(red[n - 1], blue[n - 1]), green[n - 1]);
//方法二:用一个二维dp数组实现动态规划:
int n = costs.size();
vector<vector<int>> dp(n, vector<int>(3));
dp[0][0] = costs[0][0];
dp[0][1] = costs[0][1];
dp[0][2] = costs[0][2];
for(int i = 1; i < n; i++)
{
dp[i][0] = min(dp[i - 1][1], dp[i - 1][2]) + costs[i][0];
dp[i][1] = min(dp[i - 1][0], dp[i - 1][2]) + costs[i][1];
dp[i][2] = min(dp[i - 1][0], dp[i - 1][1]) + costs[i][2];
}
return min(min(dp[n - 1][0], dp[n - 1][1]), dp[n - 1][2]);
}
};
算法总结及流程解析:


结束语
到此,13.删除并获得点数,14.粉刷房子 这两道算法题就讲解完了。**删除并获得点数,通过构建hash数组转化为打家劫舍问题,定义f[i]和g[i]分别表示选择/不选i位置时的最大点数,状态转移后取最大值。粉刷房子,建立dp[i][0-2]表示i位置刷红/蓝/绿的最小花费,通过比较前一个位置其他颜色的最小花费加上当前成本来更新状态,最终返回三种颜色的最小花费。**希望大家能有所收获!