持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第27天,点击查看活动详情
309. 最佳买卖股票时机含冷冻期
思路
(动态规划) O(n)
状态表示: f[i]表示第i 天结束后不持有股票的最大收益,g[i] 表示第 i 天结束后持有股票的最大收益。
状态计算:
f[i] = max(f[i - 1], g[i - 1] + prices[i]),表示第i天什么都不做,或者卖掉持有的股票。g[i] = max(g[i - 1], f[i - 2] - prices[i]), 表示第i天什么都不做,或者买当天的股票,但需要从上两天的结果转移。
初始化: f[0] = 0, g[0] = -prices[0]。
第0天收益为0,但为了持有股票,收益则为 0 - prices[0]。
时间复杂度分析: 状态数量为O(n),状态计算为O(1),故总的时间复杂度为O(n)。
c++代码
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
vector<int>f(n + 1), g(n + 1);
f[0] = 0, g[0] = -prices[0];
for(int i = 1; i < n; i++){
f[i] = max(f[i - 1], g[i - 1] + prices[i]);
if(i >= 2) g[i] = max(g[i - 1], f[i - 2] - prices[i]);
else g[i] = max(g[i - 1], -prices[i]);
}
return f[n - 1];
}
};
312. 戳气球
思路
(动态规划) O(n)
状态表示: f[i][j]表示戳破区间(i, j) (开区间)所有气球所能获得硬币的最大数量。
状态计算:
假设最后一次戳破编号为k的气球:
f[i][j] = max(f[i][j], f[i][k] + f[k][j] + a[i] * a[k] * a[j])
时间复杂度分析: 三重循环O(n^3)。
c++代码
class Solution {
public:
int maxCoins(vector<int>& nums) {
int n = nums.size();
vector<int> a(n + 2, 1); //全部初始化为1
for(int i = 1; i <= n; i++) a[i] = nums[i - 1]; //下标从1开始
vector<vector<int>> f(n + 2, vector<int>(n + 2));
for(int len = 3; len <= n + 2; len++) //枚举长度
for(int i = 0; i + len - 1 <= n + 1; i++){ //[0, n + 1] //左边界
int j = i + len - 1; // (i,j) 右边界
for(int k = i + 1; k < j; k++)
f[i][j] = max(f[i][j], f[i][k] + f[k][j] + a[i] * a[j] * a[k]);
}
return f[0][n + 1];
}
};
322. 零钱兑换
思路
(动态规划,完全背包问题) O(nm)
完全背包问题。
相当于有n 种物品,每种物品的体积是硬币面值,价值是1,每种物品可用无限次。问装满背包最少需要多少价值的物品?
先考虑二维状态
状态表示: f[i][j] 表示从前i种硬币中选,且总金额恰好为j的所需要的最少硬币数。
那么f[n][amount]就表示表示 从前n种硬币中选,且总金额恰好为amount的所需要的最少硬币数,即为答案。
集合划分:
按照第i种硬币可以选 0个,1个,2个,3个,,,,k个划分集合 f[i][j]。其中k*w[i] <= j,也就是说在背包能装下的情况下,枚举第i种硬币可以选择几个。
不使用第i种硬币,状态表示: f[i-1][j]
使用第i种硬币,假设我们使用k个(容量允许的情况下),状态表示:min(f[i-1][j - k*coin]) + k
状态计算方程:
f[i][j] = min(f[i-1][j],f[i-1][j-coins[i]] + 1,f[i-1][j-2*coins[i]] + 2,......,f[i-1][j-k*coins[i]] + k) .
初始化条件:
f[0][0]=0,其余f[0][j] = INF,表示当没有任何硬币的时候,存在凑成总和为 0 的方案,方案所使用的硬币为 0;凑成其他总和的方案不存在。
c++代码
class Solution {
public:
int INF = 1000000000;
int coinChange(vector<int>& coins, int amount) {
int n = coins.size();
vector<vector<int>> f (n + 1, vector<int>(amount + 1, INF));
f[0][0] = 0;
for(int i = 1; i <= n; i++)
{
int val = coins[i-1];
for(int j = 0; j <= amount; j++)
for(int k = 0; k*val <= j; k++)
{
f[i][j] = min(f[i][j] , f[i-1][j-k*val] + k);
}
}
if (f[n][amount] == INF) f[n][amount] = -1;
return f[n][amount];
}
};
时间复杂度分析: 共有 n * amount 个状态需要转移,每个状态转移最多遍历amount次。整体复杂度为O(n * amount^2)
超出时间限制,考虑一维优化。
一维优化
v代表第i件物品的体积(面值)
f[i][j] = min( f[i-1][j],f[i-1][j-v] + 1,f[i-1][j-2v] + 2......f[i-1][j-kv] + k)
f[i][j-v] + 1 = min(f[i-1,[j-v] + 1,f[i-1][j-2v] + 2......,f[i-1][j-kv] + k)
因此:
f[i][j] = min(f[i-1][j],f[i][j-v] + 1)
图示:
去掉一维:
状态计算方程为: f[j] = min([j],[j-v] + 1)
物品的体积即硬币面值: f[j] = min([j],[j-coins[i]] + 1)
时间复杂度分析:令 n 表示硬币种数,m 表示总价钱,则总共两层循环,所以时间复杂度是 O(nm)。
c++代码
class Solution {
public:
int INF = 1000000000;
int coinChange(vector<int>& coins, int amount) {
vector<int> f(amount + 1, INF);
f[0] = 0;
for (int i = 0; i < coins.size(); i ++ )
for (int j = coins[i]; j <= amount; j ++ )
f[j] = min(f[j], f[j - coins[i]] + 1);
if (f[amount] == INF) f[amount] = -1;
return f[amount] ;
}
};