题目:Best Time to Buy and Sell Stock

给定一个数组,数组中一个元素表示一天的股价,求一次交易能得到的最大收益。

思路:

数组可能是多个升序降序组成,只要能找到一组极值,使它们的差最大就可以了。

这样实际上就是每当找到一个极大值,就判断此时的到的差值是否比记录的最大值大,是则替换;

而当找一个极小值,就判断它是否小于记录的极小值,是则替换。

注意:这组极值不一定是最值,因为最大值可能在最小值的前面。

int LeetCode::maxProfit(vector<int>& prices){
    if (!prices.size())return 0;
    int start = prices.at(0), end = 0;
    int max = 0;
    for (size_t i = 1; i < prices.size(); i++){
        if (prices.at(i) < prices.at(i - 1)){//降序
            end = prices.at(i - 1);
            if (end - start > max)max = end - start;//计算上一次升序的利益值,如果大于当前最大值,则更新最大值
            if (prices.at(i) < start)start = prices.at(i);//比较下降是的值是否小于记录的起始值,是则更新起始值
        }
    }
    if (prices.at(prices.size() - 1) - start > max)max = prices.at(prices.size() - 1) - start;
    return max;
}

题目:Best Time to Buy and Sell StockII

相对于上一题,此道题没有限制交易次数,也就是说可以交易无数次,但是买之前,必须要全部卖完;

思路:

找到所有升序的范围,将它们的差值加起来,就能得到最大利益;

和上面思路类似,只是在比较最大值的地方改为累加起来,同时要更新记录的极小值。

int LeetCode::maxProfit2(vector<int>& prices){
    if (!prices.size())return 0;
    int start = prices.at(0), end = 0;
    int max = 0;
    for (size_t i = 1; i < prices.size(); i++){
        if (prices.at(i) < prices.at(i - 1)){//降序
            end = prices.at(i - 1);
            if (end - start > 0){
                max += end - start;//计算上一次升序的利益值,如果大于当前最大值,则更新最大值
                start = prices.at(i);
            }
            if (prices.at(i) < start)start = prices.at(i);//比较下降是的值是否小于记录的起始值,是则更新起始值
        }
    }
    if (prices.at(prices.size() - 1) - start > 0)max += prices.at(prices.size() - 1) - start;
    return max;
}

题目:Best Time to Buy and Sell StockIII

这次是限制交易次数为两次,其他的相同。

思路1:

因为次数是固定两次,因此情况是有限的且可以确定的;因此可以分情况讨论:

设min为所有天数中最小的价格,其对应的天数为mini;max为所有天数中最大的价格,其对应的天数为maxi。

当mini < maxi时,已经会有一个最大收益为max - min,只需找到0-mini,maxi-size之间的最大差值和mini-maxi之间的最大反向差值(先大后小)中差值大的,加上max-min 就是最终的最大收益。

当mini > maxi时,先找到0-maxi,mini-size之间的最大差值,在在maxi-mini之间递归按上面的两种情况求最大收益的对应最大和次大的获益值(不能把和返回)。

数学公式化就是下面这样:(maxProfit是一个函数,下面第二个等式是表示递归,profit是前面第一道题目中的求解方法可以求出来)

maxProfit = max - min + max{profit(0-mini),profit(mini-maxi),profit(maxi-size)}(mini < maxi)

maxProfit = max{maxProfit(0-mini),maxProfit(mini-maxi),maxProfit(maxi-size)}(maxi < mini)

pair<int, int> LeetCode::maxProfit3(vector<int>& prices, int i, int j){
    if (j - i < 2)return{0,0};
    int mini = i, maxi = i;
    int min = prices.at(i), max = prices.at(i);
    for (int k = i + 1; k < j; ++k){
        if (prices.at(k) > prices.at(maxi)){
            maxi = k;
        }
        else if (prices.at(k) < prices.at(mini)){
            mini = k;
        }
    }
    max = prices.at(maxi);
    min = prices.at(mini);
    if (mini < maxi){
        //sndPro第二大的最大获利值,start起始价格
        int sndPro = 0,start = prices.at(i);
        for (int k = i + 1; k <= mini; ++k){//结束点是整个数组的最小值,此时必定是下降的
            if (prices.at(k) < prices.at(k - 1)){//降序时
                //前面升序中获益比记录的最大值还大则,更新最大值
                if (prices.at(k - 1) - start > sndPro)sndPro = prices.at(k - 1) - start;
                if (prices.at(k) < start)start = prices.at(k);//如果下降后的价格比记录的最小价格还小,则更新最小价格
            }
        }
        start = prices.at(mini);
        for (int k = mini + 1; k <= maxi; ++k){//最大反向差;结束点是整个数组的最大值,此时必定是上升的
            if (prices.at(k)> prices.at(k - 1)){//升序时
                //下降的差值大于记录的第二大最大差值时,更新它
                if (start - prices.at(k - 1) > sndPro)sndPro = start - prices.at(k - 1);
                if (prices.at(k) > start)start = prices.at(k);//升序时的价格大于记录的最大价格时更新它
            }
        }
        start = prices.at(maxi);
        for (int k = maxi + 1; k < j; ++k){
            if (prices.at(k) < prices.at(k - 1)){//降序时
                //前面升序中获益比记录的最大值还大则,更新最大值
                if (prices.at(k - 1) - start > sndPro)sndPro = prices.at(k - 1) - start;
                if (prices.at(k) < start)start = prices.at(k);//如果下降后的价格比记录的最小价格还小,则更新最小价格
            }
        }
        //可能最后是升序结束没有更新最后一次的获利
        if (prices.at(j - 1) - start > sndPro)sndPro = prices.at(j - 1) - start;
        return{ max - min, sndPro };
    }
    else{
        //first是第一大,second是第二大
        pair<int, int>p1 = maxProfit3(prices, i, maxi + 1);
        pair<int, int>p2 = maxProfit3(prices, maxi + 1, mini);
        pair<int, int>p3 = maxProfit3(prices, mini, j);
        if (p1.first < p2.first){//合并最大值和第二大值
            if (p2.second > p1.first)p1.second = p2.second;
            else p1.second = p1.first;
            p1.first = p2.first;
        }
        else if (p2.first > p1.second)p1.second = p2.first;

        if (p1.first < p3.first){
            if (p3.second > p1.first)p1.second = p3.second;
            else p1.second = p1.first;
            p1.first = p3.first;
        }
        else if (p3.first > p1.second)p1.second = p3.first;
        return p1;
    }
    return{ 0, 0 };
}

思路2:

分类讨论情况还是很复杂的,很容易出错;而这类题目是可以使用动态规划来解决的。

P(k,i)表示到第i天的价格时,做了k次交易时的获利。

动态规划:P(k,i) = max{P(k,i-1),max{P(k-1,j) + pi - pj}(0 <= j <= i -1)};

        = max{P(k,i-1),pi + max{P(k-1,j) - pj}(0 <= j <= i -1)};

可以设temp = max{P(k-1,j) - pj}(0 <= j <= i -1);

每次更新和P(k,i)一起更新temp,使得复杂度为O(n)。

初始条件:

P(0,i) = 0;还没有做任何交易,没有获利

P(k,0) = 0;第一天的获利必然为0

int LeetCode::maxProfit3(vector<int>& prices){
    if (prices.size() < 2)return 0;
    vector<vector<int>>arr(2,vector<int>(prices.size(),0));//k为2
    int maxPro = 0;
    //temp0交易一次的temp;temp1交易2次的temp
    int temp0 = -prices.at(0),temp1 = arr.at(0).at(0) - prices.at(0);
    for (size_t i = 1; i < prices.size(); i++){
        arr.at(0).at(i) = max(arr.at(0).at(i - 1), prices.at(i) + temp0);//求P(0,i)
        temp0 = max(temp0, -prices.at(i));//更新temp0
        arr.at(1).at(i) = max(arr.at(1).at(i - 1), prices.at(i) + temp1);//求P(1,i)
        temp1 = max(temp1, arr.at(0).at(i) - prices.at(i));//更新temp1
        maxPro = max(maxPro, arr.at(1).at(i));//求最大值
    }
    return maxPro;
}

题目:Best Time to Buy and Sell StockIV

这题不再是固定的两次,而是k次,其他相同;

很显然不能再分类讨论了,那么只能使用动态规划;

思路1:

做法和上面的类似,只是2变成了k,空间复杂度变成了O(kn);

int LeetCode::maxProfit4(int k, vector<int>& prices){
    int len = prices.size();
    if (len < 2) return 0;
    if (k > len / 2){//能交易次数在天数的一半以上,则表示可以随意交易,等同于maxProfit2的算法
        int ans = 0;
        for (int i = 1; i<len; ++i){
            ans += max(prices[i] - prices[i - 1], 0);//避开小于0的情况
        }
        return ans;
    }
    vector<vector<int>>arr(k + 1, vector<int>(prices.size(), 0));
    int maxPro = 0;
    vector<int> temp(k + 1, -prices.at(0));
    for (size_t i = 1; i < prices.size(); i++){
        for (int j = 1; j <= k; ++j){
            arr.at(j).at(i) = max(arr.at(j).at(i - 1), temp.at(j) + prices.at(i));//求P(k,i)
            temp.at(j) = max(temp.at(j), arr.at(j - 1).at(i) - prices.at(i));//求temp(k)
        }
        maxPro = max(maxPro, arr.at(k).at(i));//求最大值
    }
    return maxPro;
}

思路2:

从上面的程序可以看出来,其实最终的最大利益值只与前一个的最大利益值和k相关,这样的话应该不需要记录所有的k个最大利益值。

但是具体该怎么做呢?

上面的每天都对应数组的一项,合并成n天公用一项;即是vector<vector<int>>arr(kn);=> vector<int>arr(k);将arr.at(k).at(0-n)合并为arr.at(k)

int LeetCode::maxProfit4(vector<int>& prices, int k){
    int len = prices.size();
    if (len < 2) return 0;
    if (k > len / 2){//能交易次数在天数的一半以上,则表示可以随意交易,等同于maxProfit2的算法
        int ans = 0;
        for (int i = 1; i<len; ++i){
            ans += max(prices[i] - prices[i - 1], 0);//避开小于0的情况
        }
        return ans;
    }
    vector<int>arr(k + 1,0);//记录当前价值下的0 - k个交易后的最大收益值
    vector<int> temp(k + 1, -prices.at(0));//temp的初值
    for (size_t i = 1; i < prices.size(); i++){
        int cur = prices.at(i);
        for (int j = 1; j <= k; ++j){
            arr.at(j) = max(arr.at(j), temp.at(j) + cur);//求P(k,i)
            temp.at(j) = max(temp.at(j), arr.at(j - 1) - cur);//求temp(k)
        }
    }
    return arr.at(k);
}