• [LeetCode] 递推思想的美妙 Best Time to Buy and Sell Stock I, II, III O(n) 解法


    题记:在求最大最小值的类似题目中,递推思想的奇妙之处,在于递推过程也就是比较求值的过程,从而做到一次遍历得到结果。

    LeetCode 上面的这三道题最能展现递推思想的美丽之处了。

    题1 Best Time to Buy and Sell Stock

    Say you have an array for which the ith element is the price of a given stock on day i.

    If you were only permitted to complete at most one transaction (ie, buy one and sell one share of the stock), design an algorithm to find the maximum profit.

    分析:

    我们已经知道股票每天的价格,求一次买卖的最大收益。

    怎么解?最大值减最小值?肯定不行,因为买必须在卖之前。

    简单的递推思想就可以解决

    对于1-n天的价格序列,假设maxProfit[i] (0 < i < n)表示第i天卖出时,所获得的最大收益。那么我们要求的值其实就是数组maxProfit[n] n个元素中的最大值。 

    那么maxProfit[i]如何求?因为卖的天已经确定,所以只要算出1到i 天 中哪一天价格最低,作为买的时间即可。

    根据递推思想,要求1到i 天 中哪一天价格最低,我们只需要比较 price[i] 和 1到i-1天内的最低价格,然后取较小值即可。

    同样,最后的结果是求maxProfit[n]中的值,我们也可以把求最大值的过程放到遍历中,每次求出 maxProfit[i],我们将它和 maxProfit[0]到maxProfit[i-1] 中选出的最大值max比较,然后更新max即可。

    因为比较的过程被放到了遍历的过程中,所以虽然使用递推思想,但是一次遍历就可以实现这个思想。

    代码:

    class Solution {
    public:
        int maxProfit(vector<int> &prices) {
            if(prices.size() == 0) return 0;
            int maxPrifit = -9999999; //存储最大利润
            int min = 9999999; //存储最小价格
            vector<int>::iterator i = prices.begin();
            vector<int>::iterator end = prices.end();
            for(; i < end; ++i){
                if(min > (*i)) min = (*i);
                if(maxPrifit < ((*i) - min))
                    maxPrifit = ((*i) - min);
            }
            return maxPrifit;
        }
    };

    题2 Best Time to Buy and Sell Stock II

    Say you have an array for which the ith element is the price of a given stock on day i.

    Design an algorithm to find the maximum profit. You may complete as many transactions as you like (ie, buy one and sell one share of the stock multiple times). However, you may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).

    分析:

    更改的地方是可以多次买卖,但是买必须在卖前。

    其实这道题才是三题中最简单的一道。

    思路就是“逢涨就买,逢跌就卖”。炒股的最基本道理,只有在已经知道所有股票价格的前提下才能精确地实现这句话 ==

    代码:

    class Solution {
    public:
        int maxProfit(vector<int> &prices) {
            if(prices.size() <= 1) return 0;
            int buy = -1; int profit = 0;
            vector<int>::iterator i = prices.begin();
            vector<int>::iterator end = prices.end();
            for(;i != end; ++i){
                if((i+1) != end && *(i+1) > *i && buy < 0) buy = (*i);
                if(((i+1) == end || *(i+1) < *i) && buy >= 0){
                    profit += (*i - buy);
                    buy = -1;
                }
            }
            return profit;
        }
    };

    题3 Best Time to Buy and Sell Stock III

    Say you have an array for which the ith element is the price of a given stock on day i.

    Design an algorithm to find the maximum profit. You may complete at most two transactions.

    Note:
    You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).

    分析:
    题1的变种,可以买卖两次,但是不能重叠。

    我的第一反应自然是分而治之,i = 1 to n,然后分别循环计算prices[1~ i], prices[i ~n] 的最大利润,相加,求出和的最大值。时间复杂度是实打实的 O(n2)。

    稍微改进一些,就是我们在计算prices[1~ i]的时候,可以使用递推,这样思路就成了,在题1的代码基础上,每次算完prices[1~ i]的最小值,紧接着用一个循环 计算prices[i ~n] 的最大值。这样prices[1~ i]的最大利润计算和 i = 1~n的迭代合并,只有一个子循环, 一定程度上减小时间复杂度,时间复杂度成了 (n-1) + (n-2) + .. + 1,但依然是 O(n2)

    实际上可以将复杂度减小到 O(n),这种方法会需要额外的 O(n)空间,但在一般编程领域,如果O(n)的空间能换来时间上减小一个数量级,还是相当好的。

    我们先考虑一次买卖prices[1 ~ i] (0 < i < n)的最大利润,这就和题1一样,所不同的是,我们将值存入 firstMaxPro数组,而不是求出总的最大利润。

    注意这里的firstMaxPro[n]数组和题目1中maxProfit[n]数组功能是不一样的。maxProfit[i] 表示一定在 i 天卖出的前提下,1~i天的最大利润。firstMaxPro[i]表示1~i天的最大利润,卖出可以在1~i天的任何时候。

    接着,我们再做一次遍历,这一次遍历,从n 到 1,为的是将第二次买卖考虑进去,在求第二次买卖的最大利润的同时,就可以加上 firstMaxPro[n]中的对应值,然后求出总和最大。返回这个最大的总和即可。

    两次n循环,时间复杂度O(n)

    代码:

    class Solution {
    public:
        int maxProfit(vector<int> &prices) {
            if(prices.size() <= 1) return 0;
            int* firstMaxPro = new int[prices.size()];
            int curMin = 9999999;
            int curMaxPro = -9999999;
            for(int i = 0; i < prices.size(); ++i){
                if(curMin > prices[i]) curMin = prices[i];
                if(curMaxPro < (prices[i] - curMin)){
                    curMaxPro = (prices[i] - curMin);
                }
                firstMaxPro[i] = curMaxPro;
            }
            
            //从尾到头遍历
            int MaxPro = -9999999;  //总的最大利润
            int curMax = -9999999;  //第二段区间上的最大价格
            for(int j = prices.size()-1; j >= 0; --j){
                if(curMax < prices[j]) curMax = prices[j];//第二次买卖的最大利润就等于curMax - prices[j]
                if(MaxPro < (firstMaxPro[j] + curMax - prices[j])){
                    MaxPro = (firstMaxPro[j] + curMax - prices[j]);
                }
            }
            
            return (MaxPro > 0 ? MaxPro : 0);
        }
    };

    结语

    这三道题其实都不难,都是递推思想的一些演变。但是这三道题可以很典型地体现递推的核心思想和其优势:将比较和择优的过程和递推过程合并,从而只需要单次遍历,在O(n)内获得结果。

  • 相关阅读:
    NSString 处理
    我的第一个IOSDemo
    NSArray创建和使用
    NSDate
    NSDictionary
    flash全屏代码
    getBounds
    运用递归随机出与上一个数不重复的数
    标签跟随鼠标移动
    保存数据到本地
  • 原文地址:https://www.cnblogs.com/felixfang/p/3644768.html
Copyright © 2020-2023  润新知