目录
题目一:按摩师
一个有名的按摩师会收到源源不断的预约请求,每个预约都可以选择接或不接。在每次预约服务之间要有休息时间,因此她不能接受相邻的预约。给定一个预约请求序列,替按摩师找到最优的预约集合(总预约时间最长),返回总的分钟数。
注意:本题相对原题稍作改动
示例 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。
按摩师不能连续工作,也就是数组中的数字不能选连续的两个,可以跳过一个,也可以跳过多个,求最大的数字是多少
分为下面五步:
①状态表示
经验 + 题目要求
dp[i] 表示:选择到 i 位置时,此时的最长预约时长
这里由题意可知,又可以细分两个情况,选和不选:
f[i]表示:选择到 i 位置时,nums[i] 必选,此时的最长预约时长
g[i]表示:选择到 i 位置时,nums[i] 不选,此时的最长预约时长
②状态转移方程
此时研究最近的一步,因为上面分了两种情况,所以这里的状态转移方程也分为两种情况:
选择 i 位置时:i - 1位置肯定不选,所以 f[i] = g[i-1] + nums[i]
不选 i 位置时:i - 1位置可选可不选,所以 g[i] = max(f[i-1], g[i-1])
③初始化
初始化为了保证不越界
因为 i 位置需要用到 i - 1 位置的值,所以 i 等于 0 就会越界,所以需要初始化 i == 0的情况
f[0]表示选择0位置时的最大时长,所以 f[0] = nums[0]
g[0]表示不选0位置时的最大时长,所以 g[0] = 0
④填表顺序
因为 g[i] 需要用到 f[i-1] 和 g[i-1],所以:
填表顺序就是从左往右,两个表同时填
⑤返回值
由于分为两种情况,选择和不选,所以返回值就选择 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);
vector<int> g(n);
// 初始化,g[0]默认为0
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又选3
所以这道题只需要分两类情况, 将上一题的思想就直接能够套上去
假设当前区间下标是 0 ~ n - 1
当偷0房屋时,1和n-1就不能偷了,因为是相连的, 只能从2 ~ n - 2 中进行上一题的思想,再加上当前偷的这个房屋的价值即可
当不偷0房屋时,其他房屋不受影响,也就是从 1 ~ n - 1 进行上一题的思想即可
接着在上面两种情况中,选择较大值作为dp[i]的值
代码如下:
class Solution
{
public:
int rob(vector<int>& nums)
{
int n = nums.size();
// 偷下标为0和不偷下标为0的情况,挑一个最大值
return max(nums[0] + Use(nums, 2, n - 2), Use(nums, 1, n - 1));
}
int Use(vector<int>& nums, int left, int right)
{
// 边界情况
if(left > right) return 0;
int n = nums.size();
// 与上一题思想一模一样
vector<int> f(n);
vector<int> 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]);
}
};
题目三:删除并获得点数
给你一个整数数组 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 个点数。
这道题的要求就是选择一个数 x 后,不能选择值为 x - 1 和 x + 1 的数了,如果有一个连续的数,例如 1 2 3 4,选了1,就不能选2,只能选3/4,这就和上面的按摩师的题目是一样的了,都是选择一个数,不能选择与其相连的
但是这道题不同的是,可能并不是连续的数,例如 2 5 5 7,此时选择 2 以后还能选择两个5和7,因为这三个数彼此并没有相差1,那么就需要想办法将这种情况转化为上面的情况
创建一个数组,将每一个数放入下标为这个数的位置,例如 2 5 5 7,也就是将2放入下标为2,两个5加起来是10,10放入下标为5的位置,7放入下标为7的位置
此时判断只需要看数组下标是否相邻即可,也就是不能连续选数组中的两个相邻下标的数
也就转化为了上面的题的做法
代码如下:
class Solution
{
public:
int deleteAndEarn(vector<int>& nums)
{
const int n = 10001;
// 将所有数映射进入arr
int arr[n] = {0};
for(auto it : nums) arr[it] += it;
// 和之前一样,f[i]表示选择i位置,g[i]表示不选i位置
vector<int> f(n), g(n);
for(int i = 1; i < n; i++)
{
f[i] = arr[i] + g[i - 1];
g[i] = max(f[i - 1], g[i - 1]);
}
return max(f[n - 1], g[n - 1]);
}
};
题目四:粉刷房子
假如有一排房子,共 n
个,每个房子可以被粉刷成红色、蓝色或者绿色这三种颜色中的一种,你需要粉刷所有的房子并且使其相邻的两个房子颜色不能相同。
当然,因为市场上不同颜色油漆的价格不同,所以房子粉刷成不同颜色的花费成本也是不同的。每个房子粉刷成不同颜色的花费是以一个 n x 3
的正整数矩阵 costs
来表示的。
例如,costs[0][0]
表示第 0 号房子粉刷成红色的成本花费;costs[1][2]
表示第 1 号房子粉刷成绿色的花费,以此类推。
请计算出粉刷完所有房子最少的花费成本。
示例 1:
输入: costs = [[17,2,17],[16,16,5],[14,3,19]] 输出: 10 解释: 将 0 号房子粉刷成蓝色,1 号房子粉刷成绿色,2 号房子粉刷成蓝色。 最少花费: 2 + 5 + 3 = 10。
示例 2:
输入: costs = [[7,6,2]] 输出: 2
题意就是每一行表示第几个房子,每一列的0、1、2表示红蓝绿三种颜色的花费
并且相邻的两个房子颜色不能相同
①状态表示
经验 + 题目要求 :以 i 位置为结尾时的最小成本
有三种颜色可选,所以分三种情况
②状态转移方程
所以状态转移方程,当 i 位置涂某一个颜色时,它的前一个位置就选两外两个颜色的最小值 + costs[i - 1][0]
所以三个状态转移方程是:
③初始化
需要初始化dp表的0号下标,我们可以在表前面提前创建一个位置,这样就不需要初始化了,只需要处理这三个位置的值是多少即可
因为填 i 位置时,需要选 i - 1 位置的另外两个颜色的最小值,又因为是填第一个位置的值,所以为了不影响后续结果,将前面0位置全部初始化为0,这样就不会影响结果,在填dp[i][1]时,就只会取它本身的值
最后需要注意下标的映射关系
④填表顺序
从左往右,一次填三个表
⑤返回值
取 dp[n][0] / dp[n][1] / dp[n][2] 的最小值,然后返回
因为dp[n][0]就表示最后一个房子涂上红色,此时的最小花费,以此类推
代码如下:
class Solution
{
public:
int minCost(vector<vector<int>>& costs)
{
int n = costs.size();
// 每种颜色前多创建一个位置,所以创建n+1行
vector<vector<int>> dp(n + 1, vector<int>(3));
// 初始化多创建的那一行应该初始化为0
// 但是创建的vector本身就默认初始化为0,所以不需要处理
for(int i = 1; i <= n; i++)
{
// i位置选红,在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];
}
// 选每一个房子的最小值
return min(min(dp[n][0], dp[n][1]), dp[n][2]);
}
};
题目五:买卖股票的最佳时机含冷冻期
给定一个整数数组prices
,其中第 prices[i]
表示第 i
天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
- 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: prices = [1,2,3,0,2] 输出: 3 解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]
示例 2:
输入: prices = [1] 输出: 0
题目的买卖股票的条件:
(1)、卖出股票后第二天不能买入,有冷冻期
(2)、不能参与多笔交易
①状态表示
因为当天可以分为三种状态:买入(0)、可交易(1)、冷冻期(2)
②状态转移方程
箭头的起点是前一天的状态,箭头指向的时当天的状态:
根据上面的图可以写出状态转移方程
dp[i][0] = max(dp[i-1][0], dp[i-1][1] - price[i])
dp[i][1] = max(dp[i-1][1], dp[i-1][2])
dp[i][2] = dp[i-1][0] + price[i]
③初始化
上述的状态转移方程中,dp[i] 中都用到了 dp[i-1],所以需要初始化0位置
当天结束以后处于买入状态,所以当天买了股票,相当于花掉了当天股票的钱
dp[0][0] = -price[0]
当天结束以后处于可交易状态,所以当天什么都没干
dp[0][1] = 0
当天结束以后处于冷冻期,所以当天买了又卖了,相当于没赚没亏
dp[0][2] = 0
④填表顺序
从左往右填表,一次填写三个表
⑤返回值
返回最后一天结束以后的最大利润
也就是dp[n-1][0]、dp[n-1][1]、dp[n-1][2]的最大利润,因为最后一天如果还是买入状态,没有卖出去股票,那肯定就不是最大利润了,所以可以不计算 dp[n-1][0] 的值
返回 max(dp[n-1][1], dp[n-1][2])
代码如下:
class Solution
{
public:
int maxProfit(vector<int>& prices)
{
int n = prices.size();
vector<vector<int>> dp(n, vector<int>(3));
// 只需要初始化dp[0][0],其他两个都是0,不需要初始化
dp[0][0] = -prices[0];
for(int i = 1; i < n; i++)
{
// 根据状态机得到的三个状态转移方程
dp[i][0] = max(dp[i-1][0], dp[i-1][1] - prices[i]);
dp[i][1] = max(dp[i-1][1], dp[i-1][2]);
dp[i][2] = dp[i-1][0] + prices[i];
}
// 返回最后一天后两种状态的最大值
return max(dp[n-1][1], dp[n-1][2]);
}
};
题目六:买卖股票的最佳时机含手续费
给定一个整数数组 prices
,其中 prices[i]
表示第 i
天的股票价格 ;整数 fee
代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
返回获得利润的最大值。
注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。
示例 1:
输入:prices = [1, 3, 2, 8, 4, 9], fee = 2 输出:8 解释:能够达到的最大利润: 在此处买入 prices[0] = 1 在此处卖出 prices[3] = 8 在此处买入 prices[4] = 4 在此处卖出 prices[5] = 9 总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8
示例 2:
输入:prices = [1,3,7,5,10,3], fee = 3 输出:6
本题可以无限次的交易,但是每次需要支付手续费,买入卖出只需要交一次手续费
①状态表示
可以选择创建一个 n * 2 的dp表,这里选择创建两个dp表
买入表示持有股票的状态,卖出表示可交易状态
②状态转移方程
同样画出状态机图,得到状态转移方程:
由于是有手续费存在的,所以确定在卖出时交手续费
f[i] = max(f[i-1], g[i-1] - price[i]);
g[i] = max(g[i-1], f[i-1] + price[i] - fee);
③初始化
同样i位置需要用到 i - 1 位置的值,所以需要初始化 0
f[0] = -price[0]
g[0] = 0
④填表顺序
从左往右,两个表一起填
⑤返回值
因为最后一天如果你手中还有股票没有卖出去,那么肯定不是最大利润,所以只需要返回g[n-1]即可
代码如下:
class Solution
{
public:
int maxProfit(vector<int>& prices, int fee)
{
int n = prices.size();
vector<int> f(n);
vector<int> g(n);
f[0] = -prices[0];
for(int i = 1; i < n; i++)
{
f[i] = max(f[i-1], g[i-1] - prices[i]);
g[i] = max(g[i-1], f[i-1] + prices[i] - fee);
}
return g[n-1];
}
};
下面是创建一个 n * 2 的dp表,其中 dp[i][0] 表示第 i 天的状态是买入,dp[i][1] 表示第 i 天的状态是卖出
class Solution
{
public:
int maxProfit(vector<int>& prices, int fee)
{
int n = prices.size();
vector<vector<int>> dp(n, vector<int>(2));
dp[0][0] = -prices[0];
for(int i = 1; i < n; i++)
{
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee);
}
return dp[n - 1][1];
}
};
题目七:买卖股票的最佳时机III
给定一个数组,它的第 i
个元素是一支给定的股票在第 i
天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 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
题目八:买卖股票的最佳时机IV
给你一个整数数组 prices
和一个整数 k
,其中 prices[i]
是某支给定的股票在第 i
天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k
笔交易。也就是说,你最多可以买 k
次,卖 k
次。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入:k = 2, prices = [2,4,1] 输出:2 解释:在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。
示例 2:
输入:k = 2, prices = [3,2,6,5,0,3] 输出:7 解释:在第 2 天 (股票价格 = 2) 的时候买入,在第 3 天 (股票价格 = 6) 的时候卖出, 这笔交易所能获得利润 = 6-2 = 4 。 随后,在第 5 天 (股票价格 = 0) 的时候买入,在第 6 天 (股票价格 = 3) 的时候卖出, 这笔交易所能获得利润 = 3-0 = 3 。
[动态规划]简单多状态题目到此结束