点赞👍👍收藏🌟🌟关注💖💖
你的支持是对我最大的鼓励,我们一起努力吧!😃😃
1.买卖股票的最佳时机
题目链接: 121. 买卖股票的最佳时机
题目分析:
买卖股票的最佳时机既可以用贪心也可以用动规,其中所有的股票问题都可以只用动规来解,但是有些题用贪心来解思路和写法是更优的。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。这句话的意思是买卖股票只能执行一次。返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
算法原理:
我们用圆圈代表数组中一个个元素。
如果我们把题意搞清楚,我们很容易想到一个暴力策略,我们要最大利润的话,无非就是在数组中挑出两个位置,一个位置是买入,一个位置是卖出,使这两个位置的值的差是最大的。此时我们有一个暴力解法可以把所有的二元组枚举出来,也就是两次for循环,第一层for依次从左往右固定一个卖出位置,第二层for依次枚举买入位置,每到一个位置就用卖出位置 - 买入位置,把所有情况的差求一个最大值。当固定完一个卖出位置之后,卖入位置往后走一步,然后买入位置继续从头开始枚举。
解法一:暴力解法,两层 for 循环的暴力枚举
但是时间复杂度是O(N^2)
有了这两层 for 循环的暴力枚举,我们可以想到一个优化方式,固定卖出位置的时候,我们想找到这一段的最大利润,我们之前是从头开始枚举。我们买入点其实就是在找以该卖出点位置的最大利润,此时贪心就来了,我们其实进行找到卖入位置之前买入的最小值就行了,然后拿卖出减去前面买入的最小值,就是这个点卖出的最大利润。所以我们仅需知道卖出之前所有买入位置的最小值就可以了。
我们求最小值如果从前往后遍历,时间复杂度是O(N),如果固定完一个卖出位置就已经知道前面买入位置的最小值,时间复杂度就是O(1),那整体时间复杂度就是O(N)
当我们把这个卖出位置的最大利润知道之后,接下来要求下一个卖出位置的最大利润,我们可以在去下一个卖出位置之前,拿之前的 prevMin 和 该卖出位置 做比较 求一个最小值,就知道 下一个卖出位置之前所有买入位置的最小值。
解法二: 贪心 + 一个变量标记 “前缀最小值”
时间复杂度O(N)
我们这个贪心策略其实适合暴力解法结果是一样的依旧把所有情况都枚举到,所以暴力是对的,贪心也是对的。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
int ret = 0, prevmin = INT_MAX;
for(int i = 0; i < n; ++i)
{
ret = max(ret, prices[i] - prevmin);// 先更新结果
prevmin = min(prevmin, prices[i]); // 在更新最⼩值
}
return ret;
}
};
动态规划:
因为这个动态规划思想其实就和上面贪心几乎是一模一样的,都是找前 i 天 的最低价格,所以我们直接给代码。
class Solution {
public:
int maxProfit(vector<int>& prices) {
// int n = prices.size();
// int ret = 0, prevmin = INT_MAX;
// for(int i = 0; i < n; ++i)
// {
// ret = max(ret, prices[i] - prevmin);// 先更新结果
// prevmin = min(prevmin, prices[i]); // 在更新最⼩值
// }
// return ret;
// 动态规划
//dp[i] 表示: 前 i 天 价格最低点
int n = prices.size(), ret = 0;
vector<int> dp(n + 1);
dp[0] = INT_MAX;
for(int i = 1; i <= n; ++i)
{
ret = max(ret, prices[i - 1] - dp[i - 1]);
dp[i] = min(dp[i - 1], prices[i - 1]);