股票買賣問題
本人是大三學生,有問題歡迎及時指出呀
文章目錄
- 股票買賣問題
- 1.[買賣股票的最佳時機](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/)
- 2.[最佳買賣股票時機含冷凍期](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/)
- 3.[買賣股票的最佳時機含手續費](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/)
- 4.[買賣股票的最佳時機 III](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iii/)
- 5.[買賣股票的最佳時機 IV](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv/)
1.買賣股票的最佳時機
暴力法
暴力法很簡答,兩層循環遍歷,獲取每一種利潤,然後再找出最大利潤
public int maxProfit(int[] prices) {
int max = 0;
//從第一個元素遍歷到倒數第二個元素
for(int i = 0;i < prices.length-1;i++){
//從第二個元素遍歷到最後一個元素,保證買入在賣出前
for(int j = i+1;j < prices.length;j++){
int profile = prices[j] - prices[i];
if(profile > max){
max = profile;
}
}
}
return max;
}
//暴力法
func maxProfit1(price []int)int{
if len(price) == 0{
return 0
}
max := 0
//第一層循環控制買入價
for i:= 0;i < len(price) - 1;i++{
//第二層循環控制賣出價
for j := i + 1;j < len(price);j++{
temp := price[j] - price[i]
max = int(math.Max(float64(temp), float64(max)))
}
}
return max
}
動態規劃
動態規劃 前i天的最大收益 = max{前i-1天的最大收益,第i天的價格-前i-1天中的最小价格}
public int maxProfitWithDp(int[] prices) {
int min = Integer.MAX_VALUE;
int max = 0;
//找出買入價後的最大賣出價
for(int i = 0;i<prices.length;i++){
if(prices[i]<min){
//找到最小買入價
min = prices[i];
}else if(prices[i] - min > max){
//找出最大利潤
max = prices[i] - min;
}
}
return max;
}
func maxProfit(prices []int) int {
if len(prices) == 0{
return 0
}
min := prices[0]
max := 0
for i := 1;i < len(prices);i++{
//找出最小買入價
min = int(math.Min(float64(min), float64(prices[i])))
//找出最大利潤
max = int(math.Max(float64(max),float64(prices[i] - min)))
}
return max
}
2.最佳買賣股票時機含冷凍期
與之前不同的點:
- 可以完成多次交易
- 每次賣出股票後需等待一天纔可以買入新股票
- 買股票前需賣出原持有股票
這個問題中存在三種狀態:
- 賣出(seld)
- 買入(hold)
- 持有股票(什麼也不幹,可能處於冷凍期也可能不處於冷凍期)(rest)
這三種狀態的狀態轉移是:
-
持有股票到賣出即hold到seld
seld[i] = hold[i - 1] + price[i]
-
持有股票(可能前一天就持有,也可能處於冷凍期沒有然後過了冷凍期後買入變爲持有)
hold[i] = max{hold[i - 1],rest[i - 1] - price[i]}
-
冷凍期(可能前一天就是冷凍期今天什麼也不幹,也可能前一天剛賣出,今天爲冷凍期)
rest[i] = max{rest[i - 1],sold[i - 1]}
用profit[i]表示前i天的最大利潤
所以動態方程爲:
profit[i] = max{sold[i - 1],rest[i - 1]}
func maxProfit(prices []int) int {
if len(prices) == 0 {
return 0
}
hold := math.MinInt8
seld := 0
rest := 0
for i := 0;i < len(prices);i++{
//記錄前一天賣出狀態
preSeld := seld
//賣出狀態:前一天持有然後當天賣出
seld = hold + prices[i]
//持有狀態:前一天本來就持有,或者前一天冷凍期過了後當天買入
hold = int(math.Max(float64(hold), float64(rest-prices[i])))
//冷凍期狀態:前一天是冷凍期,或者前一天剛好賣出
rest = int(math.Max(float64(rest), float64(preSeld)))
}
return int(math.Max(float64(rest), float64(seld)))
}
public int maxProfit(int[] prices) {
int sold = 0;
int rest = 0;
int hold = Integer.MIN_VALUE;
for(int i : prices){
//記錄前一天賣出
int preSold = sold;
//狀態轉移方程:
// newHold = Max{oldHold,rest - price[i]}
// newSold = hold + price[i]
// newRest = Max{oldRest,oldSold}
sold = hold + i;
hold = Math.max(hold,rest - i);
rest = Math.max(preSold,rest);
}
return Math.max(sold,rest);
}
3.買賣股票的最佳時機含手續費
這道題目就沒有了冷凍期的概念,即賣出後可以立即買入,但是每一次交易(買入和賣出)都需要付一次手續費
這道題目我用一個二位數組來表示狀態:
dp[i][0]:第i天不持有股票的最大利潤
dp[i][1]:第i天持有股票的最大利潤
分析狀態轉移:
從第 i - 1天狀態到第 i 天 持有狀態:
- i - 1天本來持有持有
- i - 1天不持有但是第i天購買
dp[i] = max(dp[i - 1][1],dp[i - 1][0] - prices[i])
從第 i - 1天狀態到第 i 天 不持有狀態:
- 第 i - 1天本來不持有
- 第 i - 1天本來持有但是第 i 天賣出並且交上手續費
dp[i][0] = max(dp[i - 1][0],dp[i - 1][1] + prices[i] - fee)
所以,最終狀態方程:
res = max(res,Math.max(dp[i][0],dp[i][1]))
分析初始狀態:
dp[0][0] = 0;
dp[0][1] = -prices[0];
if len(prices) == 0{
return 0
}
var dp [50001][2]int
res := 0
dp[0][0] = 0
dp[0][1] = -prices[0]
for i := 1;i < len(prices);i++{
dp[i][0] = int(math.Max(float64(dp[i-1][0]), float64(dp[i-1][1] + prices[i] - fee)))
dp[i][1] = int(math.Max(float64(dp[i - 1][1]),float64(dp[i - 1][0] - prices[i])))
res = int(math.Max(float64(dp[i][0]),float64(dp[i][1])))
}
return res
public int maxProfit(int[] prices, int fee) {
if(prices.length == 0){
return 0;
}
int[][] dp = new int[prices.length][2];
int res = 0;
dp[0][0] = 0;
dp[0][1] = -prices[0];
for(int i = 1;i < prices.length;i++){
dp[i][1] = Math.max(dp[i - 1][1],dp[i - 1][0] - prices[i]);
dp[i][0] = Math.max(dp[i - 1][0],dp[i - 1][1] + prices[i] - fee);
res = Math.max(res,Math.max(dp[i][0],dp[i][1]));
}
return res;
}
4.買賣股票的最佳時機 III
這道題目跟之前又有不同了,沒有了冷凍期,沒有了手續費,但是限制了交易次數,只能交易兩次
分析狀態:
dp[i][1][0]:第一次交易第i天賣出
dp[i][1][1]:第一次交易第i天買入
dp[i][2][0]:第二次交易第i天賣出
dp[i][2][1]:第二次交易第i天買入
狀態轉移分析:
dp[i][1][1] --》 dp[i][1][0] --》 dp[i][2][1] --》 dp[i][2][0]
所以:
func maxProfit(prices []int) int {
firstBuy := math.MinInt64
firstSeld := 0
secondBuy := math.MinInt64
secondSeld := 0
for i := 0;i < len(prices);i++{
firstBuy = int(math.Max(float64(firstBuy),float64(-prices[i])))
firstSeld = int(math.Max(float64(firstSeld),float64(firstBuy + prices[i])))
secondBuy = int(math.Max(float64(secondBuy),float64(firstSeld - prices[i])))
secondSeld = int(math.Max(float64(secondSeld),float64(secondBuy + prices[i])))
}
return secondSeld
}
public int maxProfit4(int[] prices) {
/**
對於任意一天考慮四個變量:
fstBuy: 在該天第一次買入股票可獲得的最大收益
fstSell: 在該天第一次賣出股票可獲得的最大收益
secBuy: 在該天第二次買入股票可獲得的最大收益
secSell: 在該天第二次賣出股票可獲得的最大收益
分別對四個變量進行相應的更新, 最後secSell就是最大
收益值(secSell >= fstSell)
**/
int firstBuy = Integer.MIN_VALUE;
int firstSell = 0;
int secondBuy = Integer.MIN_VALUE;
int secondSell = 0;
for(int i : prices){
firstBuy = Math.max(firstBuy,-i);
firstSell = Math.max(firstSell,firstBuy + i);
secondBuy = Math.max(secondBuy,firstSell - i);
secondSell = Math.max(secondSell,secondBuy + i);
}
return secondSell;
}
5.買賣股票的最佳時機 IV
這道題目就是上一道的上升了,最多完成k次交易
分析狀態:
dp[i][0]:i次交易下來不持有股票
dp[i][1]:i次交易下來持有股票
分析狀態轉移:
i 次交易下來持有股票,可能是 i 次下來持有股票,或者 i- 1次交易下來不持有股票,但是在第 i 次交易中,買入股票,交易次數+1,達到i次,狀態爲持有股票
dp[i][1] = Math.max(dp[i][1],dp[i - 1][0] - p)
i 次交易下來不持有股票,可能是 i 次下來不持有股票,或者 i次交易下來持有股票,但是在第 i 次交易中,賣出股票,狀態爲不持有股票
dp[i][0] = Math.max(dp[i][0],dp[i][1] + p)
所以
public int maxProfit(int k, int[] prices) {
if(k < 1){
return 0;
}
//當k超過一半時,改用貪心算法
if(k >= prices.length/2){
return greedy(prices);
}
//dp[i][0]:第i天不持有
//dp[i][1]:第i天持有
int[][] dp = new int[k][2];
for(int i = 0; i < k; ++i) {
dp[i][1] = Integer.MIN_VALUE;
}
for(int p : prices){
//第一次交易
dp[0][1] = Math.max(dp[0][1],-p);
dp[0][0] = Math.max(dp[0][0],dp[0][1] + p);
//剩下k-1次
for(int i = 1;i < k;i++){
dp[i][1] = Math.max(dp[i][1],dp[i - 1][0] - p);
dp[i][0] = Math.max(dp[i][0],dp[i][1] + p);
}
}
return dp[k - 1][0];
}
private int greedy(int[] prices) {
int max = 0;
for(int i = 1; i < prices.length; ++i) {
if(prices[i] > prices[i-1]) {
max += prices[i] - prices[i - 1];
}
}
return max;
}