1.题目链接:按摩师
2.题目描述:一个有名的按摩师会收到源源不断的预约请求,每个预约都可以选择接或不接。在每次预约服务之间要有休息时间,因此她不能接受相邻的预约。给定一个预约请求序列,替按摩师找到最优的预约集合(总预约时间最长),返回总的分钟数。
示例 1:
输入: [1,2,3,1]
输出: 4 解释: 选择 1 号预约和 3 号预约,总时长 = 1 + 3 = 4。
示例 2:
输入: [2,7,9,3,1]
输出: 12 解释: 选择 1 号预约、 3 号预约和 5 号预约,总时长 = 2 + 9 + 1 = 12。
示例 3:
输入: [2,1,4,5,3,1,1,3]
输出: 12 解释: 选择 1 号预约、 3 号预约、 5 号预约和 8 号预约,总时长 = 2 + 4 + 3 + 3 = 12。
3.问题分析:
对于动态规划多状态这类问题,看完题目后,对于某个事物,有明显几个状态;比如这道题的事物就是这个按摩师,这个按摩师可能有工作,休息两种状态,而我们要做的就是将这两种状态表示出来,然后计算不同状态下他所能达到的值。 最后依据题目来求解最大值还是最小值。
f[i] = nums[i] + g[i - 1];g[i] = max(f[i - 1], g[i - 1]);
f[0]=nums[0]
;对于g[i]来说,i位置表示不选,所以g[0]=0
。max(f[n - 1], g[n - 1])
。代码如下:
class Solution
{
public:
int massage(vector<int>& nums)
{
int n = nums.size();
//判断是否为空
if (n == 0)
return 0;
vector<int> f(n), g(n);
//初始化
f[0] = nums[0];
for (int i = 1; i < n; ++i)
{
f[i] = nums[i] + g[i - 1];
g[i] = max(f[i - 1], g[i - 1]);
}
return max(f[n - 1], g[n - 1]);
}
};
1.题目链接:打家劫舍II
2.题目描述:你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
示例 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
3.问题分析:
这道题上道题的区别就是将这个nums数组看作是一个环形数组,这样的话就需要考虑临界区,也就是对首尾的状况进行分析;具体有如下几种:如果第一个位置选,那么最后一个位置就不能选,也就是在区间[0,n - 2];如果最后一个位置选,那么第一个位置就不能选,也就是区间[1,n - 1]。 还有没有别的情况?没有了。然后对两个区间[0,n - 2]和[1,n - 1]
分别进行一次按摩师的问题,就可以求出最大值。
f[i] = nums[i] + g[i - 1];g[i] = max(f[i - 1], g[i - 1]);
f[0]=nums[0]
;对于g[i]来说,i位置表示不选,所以g[0]=0
。max(f[n - 1], g[n - 1])
。代码如下:
class Solution
{
public:
int _rob(vector<int>& nums, int left, int right)
{
if (left > right)
return 0;
int n = nums.size();
vector<int> f(n), g(n);
//初始化第一个值
f[left] = nums[left];
for (int i = left + 1; i <= right; ++i)
{
f[i] = nums[i] + g[i - 1];
g[i] = max(f[i - 1], g[i - 1]);
}
return max(f[right], g[right]);
}
int rob(vector<int>& nums)
{
int n = nums.size();
//判断临界条件
if (n == 1)
return nums[0];
//分别求出两个区间[0,n - 2]和[1,n - 1]的最大值
return max(_rob(nums, 0, n - 2), _rob(nums, 1, n - 1));
}
};
1.题目链接:删除并获得点数
2.题目描述:
给你一个整数数组 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 * 10^4
1 <= nums[i] <= 10^4
3.问题分析:
这道题就需要很好的理解能力了,大概意思是给你一个数组,然后你可以删除一个元素并获得它的大小(这里就叫做点数),但是你如果删除了某个数,比如5,那么你就不能再删除数组中的3和4(上述加红区域)。看到这会想到什么??是不是就和上述第一题差不多了,i位置必选的话,i-1与i+1位置就不能再选。然后就需要一些操作,将本题变为与上述题一样的解法即可,思路如下:由提示可知道1 <= nums[i] <= 10^4,所以用一个10001大小的数组将数据管理起来,下标对应的就是每个数据,所存的元素表示该下标总共的和(例如4出现3次,那么4的位置所存的数据就为12)。
f[i] = hash[i] + g[i - 1];g[i] = max(f[i - 1], g[i - 1]);
代码如下:
class Solution
{
public:
int deleteAndEarn(vector<int>& nums)
{
const int N = 10001;
int hash[N] = { 0 };
for (auto& x : nums)
hash[x] += x;
vector<int> f(N), g(N);
for (int i = 1; i < N; ++i)
{
f[i] = hash[i] + g[i - 1];
g[i] = max(f[i - 1], g[i - 1]);
}
return max(f[N - 1], g[N - 1]);
}
};
1.题目链接:买卖股票的最佳时机含冷冻期
2.题目描述:给定一个整数数组prices,其中第 prices[i] 表示第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: prices = [1,2,3,0,2]
输出: 3 解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]
示例 2:
输入: prices = [1]
输出: 0
提示:
1 <= prices.length <= 5000
0 <= prices[i] <= 1000
3.问题分析:
这类题先分状态,然后对每种状态再进行详细讨论。如本题状态该怎么分?在某一天的交易状态可能为买入、冷冻期,冷冻期之后为可交易状态(可交易状态是需要自行找出的)因为卖出的那一刻和进入冷冻期的利润相同,所以买卖股票问题不需要关心卖出状态(如果考虑卖出状态的话,要记得冷冻期的利润是和当天卖出状态利润相同的)。
dp[0][j] 表⽰:第 i 天结束后,处于「买⼊」状态,此时的最⼤利润;dp[1][j] 表⽰:第 i 天结束后,处于「冷冻期」状态,此时的最⼤利润;dp[2][j] 表⽰:第 i 天结束后,处于「可交易」状态,此时的最⼤利润
。箭头末尾表示昨天,箭头前端表示今天。nothing表示什么也不用做。 由买入状态开始分析:由买入能不能到买入状态?什么也不干就可以,由买入到冷冻期?昨天卖出,今天就到冷冻期 ,由买入到可交易?买入后只能卖出到冷冻期,所以不可行。 冷冻期状态:由冷冻期到冷冻期?不行,由冷冻期到买入,显而易见也不行,由冷冻期到可交易?昨天冷冻期,什么也不干,今天就能到达可交易。可交易状态:由可交易到可交易?昨天可交易状态什么也不干,今天就又是可交易状态,由可交易到买入?昨天可交易,今天当然可以买入,由可交易到冷冻期?不行。
所以状态转移方程如下:dp[0][j] = max(dp[0][j - 1], dp[2][j - 1] - prices[j]); dp[1][j] = dp[0][j - 1] + prices[j]; dp[2][j] = max(dp[2][j - 1], dp[1][j - 1]);
代码如下:
class Solution
{
public:
int maxProfit(vector<int>& prices)
{
int n = prices.size();
vector<vector<int>> dp(3, vector<int>(n));
dp[0][0] = -prices[0];
for (int j = 1; j < n; ++j)
{
dp[0][j] = max(dp[0][j - 1], dp[2][j - 1] - prices[j]);
dp[1][j] = dp[0][j - 1] + prices[j];
dp[2][j] = max(dp[2][j - 1], dp[1][j - 1]);
}
return max(dp[1][n - 1], dp[2][n - 1]);
}
};
1.题目链接:买卖股票的最佳时机III
2.题目描述:给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔(k笔) 交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入:prices = [3,3,5,0,0,3,1,4]
输出:6 解释:在第 4 天(股票价格 = 0)的时候买入,在第6天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。
随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。
示例 2:
输入:prices = [1,2,3,4,5]
输出:4 解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这个情况下, 没有交易完成, 所以最大利润为 0。
示例 4:
输入:prices = [1]
输出:0
提示:
1 <= prices.length <= 10^5
0 <= prices[i] <= 10^5
3.问题分析:
这道题与上道题相比,多需要考虑一个交易次数,所以需要再增加一个维度用来表示交易次数。对于状态来说,只有买入状态和可交易状态,所以用f,g两个数组来进行表示,第二个维度j表示的就是交易次数。
f[i][j] 表⽰:第 i 天结束后,完成了 j 次交易,处于「买⼊」状态,此时的最⼤利润; g[i][j] 表⽰:第 i 天结束后,完成了 j 次交易,处于「卖出」状态,此时的最⼤利润。
g[i][j] = g[i - 1][j]; if(j >= 1) g[i][j] = max(g[i][j], f[i - 1][j - 1] + prices[i]);
代码如下:
class Solution {
public:
const int INF = 0x3f3f3f3f;
int maxProfit(vector<int>& prices) {
int n = prices.size();
vector<vector<int>> f(n, vector<int>(3, -INF)), g(n, vector<int>(3, -INF));
f[0][0] = -prices[0], g[0][0] = 0;
for (int i = 1; i < n; ++i)
{
for (int j = 0; j < 3; ++j)
{
f[i][j] = max(f[i - 1][j], g[i - 1][j] - prices[i]);
if (j >= 1) //防止越界
g[i][j] = max(g[i - 1][j], f[i - 1][j - 1] + prices[i]);
else
g[i][j] = g[i - 1][j];
}
}
int ret = 0;
for (int j = 0; j < 3; ++j)
ret = max(ret, g[n - 1][j]);
return ret;
}
} ;
对于买卖股票的最佳时机IV问题来说,交易k次,分析方式与上道题基本无异,只需将上述代码中的3改为k即可。