leetcode之股票问题

17 篇文章 0 订阅

昨天在刷题时碰上了一个系列算法题,叫做股票问题,刚开始做的还行,做着做着发现事情没有想象的那么简单,于是记录一下。

  1. best-time-to-buy-and-sell-stock-ii

题目描述:
假设你有一个数组,其中第i个元素表示某只股票在第i天的价格。
设计一个算法来寻找最大的利润。你可以完成任意数量的交易(例如,多次购买和出售股票的一股)。但是,你不能同时进行多个交易(即,你必须在再次购买之前卖出之前买的股票)。

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() == 0)
            return 0;
        int ans = 0;
        int buy_prices = INT_MAX;
        int sell_prices = INT_MAX;
        for (auto val : prices)
        {
            if (val < buy_prices && buy_prices == sell_prices)
                buy_prices = sell_prices = val;
            else if (val >= sell_prices)
                sell_prices = val;
            else
            {
                ans += (sell_prices - buy_prices);
                buy_prices = sell_prices = val;
            }
        }
        ans += (sell_prices - buy_prices);
        return ans;
    }
};

我的这个思路其实是复杂了,但是思路说到底也就是找到所有递增的区间
大佬们的代码:

public class Solution {
    public int maxProfit(int[] prices) {
        if(prices == null || prices.length<2)
            return 0;
        int res = 0;
        for(int i =1;i<prices.length;i++){
            if(prices[i]>prices[i-1])
                res += prices[i]-prices[i-1];
        }
        return res;
    }
}
  1. best-time-to-buy-and-sell-stock-iii

题目描述:
假设你有一个数组,其中第i个元素是某只股票在第i天的价格。
设计一个算法来求最大的利润。你最多可以进行两次交易。
注意:
你不能同时进行多个交易(即,你必须在再次购买之前出售之前买的股票)。

Say you have an array for which the i th 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).

思路:

  • 刚看到这道题的时候觉得和上面一题差不多嘛,只不过只让买卖两次了,那我找到两个递增区间最大的两个地方不就好了,定义两个变量first和second存这两个值最后加起来OK了!
  • 后来发现并不是这样,因为有时候在一次买卖区间内可能既有递增又有递减,所以只找递增区间是不够的。
  • 然后没有思路了,只能求助大佬。

大佬告诉我可以这样做:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int buy1 = INT_MIN, sell1 = 0, buy2 = INT_MIN, sell2 = 0;
        for(int i = 0; i < prices.size(); i++) {
            buy1 = max(buy1, -prices[i]);
            sell1 = max(sell1, buy1 + prices[i]);
            buy2 = max(buy2, sell1 - prices[i]);
            sell2 = max(sell2, buy2 + prices[i]);
        }
        return sell2;
    }
};

嗯,看不懂。
大佬再解释:

/*
题目意思: 数组下标为i的元素存储股票i天时的价格,要求进行2次交易,求出最大利益,并且买第2支股票前必须先抛售第一支股票
假如只进行1次交易的话会更简单一些:用sell1表示初始时身上的净利润为0,buy1之后用于存放最便宜股价的价格。一个循环表示时间一天天推移,第一天时buy1记录下第一天买入股票身上净利润,之后每进入新的一天(今天),就用buy1表示前些天最便宜的股价,sell1保存了前些天买入最便宜股票之后再在股价最高时卖出股票的最大利润。新的一天到来,再用buy1继续记录最低股价,再计算出在今天抛售那个最低价股票后的利润,如果这个利润比之前保存的sell1高,那就更新sell1,否则,sell1不变。
如此循环下去,到最后一天结束,sell1就记录了一次交易的最大利润。
进行2次交易的道理是可以类推的。
*/
class Solution {
public:
int maxProfit(vector<int> &prices) {
int buy1 = INT_MIN, sell1 = 0, buy2 = INT_MIN, sell2 = 0;
for(auto u:prices) { //每次循环表示进入新的一天
buy1 = max(buy1,-u); //记录之前所有天最便宜的股价
sell1 = max(sell1,buy1 + u); //记录之前所有天只进行1次买卖的最大利益
buy2 = max(buy2,sell1 - u); //记录之前天先进行1次交易获得最大利益后,
//再买到那次交易后最便宜股价时剩余的净利润
sell2 = max(sell2,buy2 + u); //记录之前天进行2次完整交易的最大利润
}
return sell2;
}
};

哦…好像看懂了一点点,但是这惊为天人的代码是怎么想到的呢?
newcode没有看到更好的解释了,于是乎去求助了leetcode上的大佬,看到了这样一篇文章:
一个通用方法团灭 6 道股票问题

作者指出所有的股票问题都可以总结成一个状态转移方程问题,通过定义一个三维数组进行求解,称为“三维dp”。

所有股票问题的状态无非可以总结为三个:天数,交易次数,当日是否持股。

于是我们可以定义一个三维数组来描述这个状态:dp[i][k][x]
其中,
i 表示第 i 天,它的取值范围为[0, day-1](day为总的天数)
k 表示已发生的交易次数,这里我们定义每次购买股票时算一次交易(文章中说 buy 时和 sell 时都可算一次交易,但是按 sell 算时 base case 的定义特别困难)
x 表示当日的持股状态,持股状态无非两种:持股和未持股。这里用 0 表示当日未持股,用 1 表示当日持股。

最后,这个状态数组的值表示当前状态的最大利润,所以 dp[i][k][0] 表示的就是在第 i 天进行 k 次交易后的最大利润(未持股肯定比持股利润大啦)。

于是乎我们可以写出状态转移方程:

  • dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
  • dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][1] - prices[i])

第一个状态方程可以解释为:
如果当天未持股,则会是两种情况
1、昨天也未持股,今天没有买股票,即dp[i-1][k][0];
2、昨天持股了,今天把股票卖了,即dp[i-1][k][1] + prices[i];
我们取这两种情况中利润最大的一种。

同理第二个状态方程可以解释为:
如果当天持股了,则会是两种情况
1、昨天也持股了,今天没有卖股票,即dp[i-1][k][1];
2、昨天未持股了,今天买了股票,即dp[i-1][k-1][0] - prices[i];
注意今天买股票算作一次交易,所以今天交易的次数要比昨天多一次,同样我们取利润最大的一种。

最后,我们考虑 base case:
1、dp[-1][k][0] = 0 // -1天时不可能买股票,所以利润为0
2、dp[-1][k][1] = INT_MIN // -1天不可能持股,我们把利润定义成负无穷,也就是无意义
3、dp[i][0][0] = 0 // 没有交易所以利润一定为0
4、dp[i][0][1] = INT_MIN // 没有交易不可能持股,所以这种情况也无意义

知道了这些,我们就可以套用状态转移方程解决股票问题了:
当只能进行一次交易时,也就是k = 1。
题目:leetcode121
代码:

class Solution {
public:
    int maxProfit(vector<int> &prices) {
        int day = prices.size();
        if (day <= 1)
            return 0;
        int (*dp)[2] = new int[day][2];
        for (int i=0; i<day; i++)
        {
            if (i == 0)
            {
                dp[i][0] = 0;
                dp[i][1] = -prices[i];
            }
            else
            {
                dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i]);
                dp[i][1] = max(dp[i-1][1], -prices[i]);
            }
        }
        return dp[day-1][0];
    }
};

不限制交易次数时,也就是k = 无穷大
题目:leetcode122
代码:

class Solution {
public:
    int maxProfit(vector<int> &prices) {
        int day = prices.size();
        if (day <= 1)
            return 0;
        int (*dp)[2] = new int[day][2];
        for (int i=0; i<day; i++)
        {
            if (i == 0)
            {
                dp[i][0] = 0;
                dp[i][1] = -prices[i];
            }
            else
            {
                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]);
            }
        }
        return dp[day-1][0];
    }
};

当限制交易次数为2时,也就是k = 2。
题目:leetcode123
代码:

class Solution {
public:
    int maxProfit(vector<int> &prices) {
        int day = prices.size();
        int k_max = 2;
        if (day <= 1)
            return 0;
        int (*dp)[3][2] = new int[day][3][2];
        for (int i=0; i<day; i++)
        {
            for (int j=1; j<=k_max; j++)
            {
                if (i == 0)
                {
                    dp[i][j][0] = 0;
                    dp[i][j][1] = -prices[i];
                }
                else
                {
                    dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1]+prices[i]);
                    dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0]-prices[i]);
                }
            }
        }
        return dp[day-1][2][0];
    }
};

当未指定 k 的具体值时:
题目:leetcode188
这题比较头痛,当我兴冲冲的打算继续按照上面的框架写答案的时候,问题来了:
怎么new一个三维数组?
说来惭愧,刷了这么久的题,连个new都弄不好,最终通过各路大佬帮忙写出了答案:

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        int day = prices.size();
        if (day <= 1)
            return 0;
        int *** dp = new int**[day];
        for (int i=0; i<day; i++)
        {
            dp[i] = new int*[k+1]; 
            dp[i][0] = new int[2];
            for (int j=1; j<=k; j++)
            {
                dp[i][j] = new int[2];
                if (i == 0)
                {
                    dp[i][j][0] = 0;
                    dp[i][j][1] = -prices[i];
                }
                else
                {
                    dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1]+prices[i]);
                    dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0]-prices[i]);
                }
            }
        }
        return dp[day-1][k][0];
    }
};

大家对new不太熟悉的同学可以看一下我是怎么new的,但是就当我势在必得的时候又出岔子了: 答案错误。
这不禁让我怀疑人生,难道看了这么久的答案是错的?在时候VS一步一步进行调试后我发现了问题所在:
new申请的内存没有初始化

原来时new的内存没有初始化,导致我的利润变成了负无穷!怪不得我赚不到钱!
修改后的代码:

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        int day = prices.size();
        if (day <= 1)
            return 0;
        int *** dp = new int**[day];
        for (int i=0; i<day; i++)
        {
            dp[i] = new int*[k+1]; 
            dp[i][0] = new int[2];
            memset(dp[i][0], 0, 2 * sizeof(int));//初始化
            for (int j=1; j<=k; j++)
            {
                dp[i][j] = new int[2];
                memset(dp[i][j], 0, 2 * sizeof(int)); //初始化
                if (i == 0)
                {
                    dp[i][j][0] = 0;
                    dp[i][j][1] = -prices[i];
                }
                else
                {
                    dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1]+prices[i]);
                    dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0]-prices[i]);
                }
            }
        }
        return dp[day-1][k][0];
    }
};

再次提交,这次错倒是没错:
在这里插入图片描述
这又是咋了?可能是数据太大这个方法还需要优化吧…不过对了209的样例应该可以说明代码是没错的…

总结:

对于股票问题,可以使用状态转移方程求解。方法如上。
可以将这类问题扩展,类似于 “求数组中其中四个数和的最大值”这样的问题

珍爱生命远离算法题

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值