原题链接:
https://leetcode.com/problems/best-time-to-buy-and-sell-stock
https://leetcode.com/problems/best-time-to-buy-and-sell-stock-II
https://leetcode.com/problems/best-time-to-buy-and-sell-stock-III
https://leetcode.com/problems/best-time-to-buy-and-sell-stock-IV
第一题
这道题比较简单,只允许交易一次,得到最大收益。只要找到两天价格差最大即可
按照此思路,代码如下:
package leetcode;
public class BestTimetoBuyAndSellStock {
// https://leetcode.com/problems/best-time-to-buy-and-sell-stock/
public static int maxProfit(int[] prices) {
int len = prices.length;
if (prices == null || len <= 1) {
return 0;
}
int minPrice = prices[0]; // 最小价格初始化
int diff = prices[1] - prices[0];// 价格差
for (int i = 2; i < len; i++) {
minPrice = Math.min(prices[i-1], minPrice);
if(diff<prices[i]-minPrice){
diff=prices[i]-minPrice;
}
}
if(diff<0){
return 0;
}
return diff;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] prices = { 1, 3, 5, 6, 1, 2, 9 };
System.out.println(maxProfit(prices) + "");
}
}
还有一种解法,维护两个变量,一个代表当前最大收益local,一个代表全局最大收益global,local是上一次当前收益加上后面两天收益的价差得到,全局收益是指全局收益与此次当前收益取最大值。这样,local的值实质上是此时卖出价格与第一次买入时的价差,global可以记录价差的最大值。大家可以运行代码单步调试来理解这个方法,代码如下:
package leetcode;
public class BestTimetoBuyAndSellStock {
// https://leetcode.com/problems/best-time-to-buy-and-sell-stock/
public static int maxProfit(int[] prices) {
if (prices == null || prices.length <= 1) {
return 0;
}
int local = 0;
int global = 0;
for (int i = 0; i < prices.length - 1; i++) {
local = Math.max(local + prices[i + 1] - prices[i], 0);// 当前最大收益
global = Math.max(local, global);// 全局最大收益
}
return global;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] prices = { 1, 3, 5, 6, 1, 2, 9 };
System.out.println(maxProfit(prices) + "");
}
}
第二题:
这里同第一次差别为可以购买无数次,这道题可以这样想,根据第一题第二种解法,可以知道当前最大收益本地值,当时只允许买卖一次,所以用一个global来记录最大收益。现在可以允许买卖无数次,所以不需要global去记录最大收益,只要收益为正的时候,就可以进行买入卖出。,所以只要前后两次价差为正即可加到收益中去。代码如下:
package leetcode;
import java.lang.reflect.Array;
public class BestTimeToBuyAndSellStockII {
// https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/
public static int maxProfit(int[] prices) {
if (prices == null || prices.length == 0) {
return 0;
}
int len = prices.length;
int maxProfit = 0;
for (int i = 1; i < len; i++) {
int tempProfit = prices[i] - prices[i - 1];
if (tempProfit > 0) {
maxProfit += tempProfit;
}
}
return maxProfit;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] prices = { 1 };
System.out.println(maxProfit(prices) + "");
}
}
第三题:
可以买入两次。这时可以用动态规划的算法来做了,但是我到第四题的时候可以买入k次条件时再用动态规划,这里用另外一种方法。
这里我们创建两个数组ArrayA与ArrayB,一个记录从前向后遍历得到的最大价差,一个记录从后向前遍历得到的最大价差。最后两个相加后得到的最大值即为最大收益。代码如下:
package leetcode;
public class BestTimetoBuyAndSellStockIII {
// https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/
public static int maxProfit(int[] prices) {
if (prices == null || prices.length == 0) {
return 0;
}
int len = prices.length;
int sum = 0;
int min = prices[0];
int[] ArrayA = new int[len];
for (int i = 1; i < prices.length; i++) {
ArrayA[i] = prices[i] - min;
ArrayA[i] = ArrayA[i] > ArrayA[i - 1] ? ArrayA[i] : ArrayA[i - 1];
if (prices[i] < min) {
min = prices[i];
}
}
int max = prices[prices.length - 1];
int[] ArrayB = new int[len];
for (int i = len - 1; i > 0; i--) {
ArrayB[i] = max - prices[i];
ArrayB[i] = ArrayB[i] > ArrayB[i - 1] ? ArrayB[i] : ArrayB[i - 1];
if (prices[i] > max) {
max = prices[i];
}
}
for (int i = 0; i < len; i++) {
sum = Math.max(ArrayA[i] + ArrayB[i], sum);
}
return sum;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] prices = { 1, 3, 5, 6, 1, 2, 9 };
System.out.println(maxProfit(prices) + "");
}
}
这个思路很容易理解,前后两次价差相加。
第四题:
买入k次,这次就只能用动态规划的思想了。之前不了解动态规划的同学,可以先看背包问题九讲!!!,讲的很好,之后我也回把背包问题的代码贴出来。
背包问题九讲链接地址:http://love-oriented.com/pack/(必看)
首先先按照第二题思路,求得最大收益值与最大交易次数(无限次条件)。如果交易次数k大于最大交易次数,则最终结果就是最大收益。
如果k小于最大交易次数,则使用动态规划的方法求解收益最大值。
第四题代码:
package leetcode;
public class BestTimetoBuyAndSellStockIV {
// https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iv/
public static int maxProwt(int k, int[] prices) {
int days = prices.length;
int tradeCount = 0;// 交易次数
int profitCount = 0;// 收益
int rangeProfitCount = 0;
for (int i = 1; i < days; i++) {
if (prices[i - 1] < prices[i]) {
// 两天之间收益为正值
rangeProfitCount += prices[i] - prices[i - 1];
if (i == days - 1) {// 最后一天
profitCount += rangeProfitCount;
tradeCount += 1; // 交易次数加一
}
} else if (rangeProfitCount > 0) {
profitCount += rangeProfitCount; // 收益加上
tradeCount += 1; // 交易次数加一
rangeProfitCount = 0;
}
}
if (k >= tradeCount) {// 如果k交易次数大于等译交易总次数,直接返回最大收益
return profitCount;
}
int[][] global = new int[k + 1][days];
int[][] local = new int[k + 1][days];
// 动态规划
for (int i = 1; i <= k; i++) {
for (int j = 1; j < days; j++) {
int diff = prices[j] - prices[j - 1];
local[i][j] = Math.max(global[i - 1][j - 1], local[i][j - 1] + diff);
global[i][j] = Math.max(global[i][j - 1], local[i][j]);
}
}
return global[global.length - 1][global[0].length - 1];
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] prices = { 1, 3, 5, 6, 1, 2, 9 };
System.out.println(maxProwt(3, prices) + "");
}
}
附:背包问题代码(包含01背包,完全背包,多重背包求解方法)
package pack;
import java.util.ArrayList;
import java.util.List;
import org.omg.CORBA.SystemException;
public class Pack {
private static int N = 4; // 物品个数
private static int V = 10; // 背包最大容量
private int[] f;// 前i件物品放入容量为j的背包得到的最大价值f[j]
private static int G=2;//分组背包问题组数
// 状态转移方程f[v]=max{f[v],f[v-cost]+weight};
public Pack() {
super();
resetF();
}
private void resetF() {
f = new int[V + 1];
for (int i = 0; i < f.length; i++) {
f[i] = 0;
}
}
/**
* 01背包
*
* @param cost
* 消耗容量
* @param weight
* 体积
* @return
*/
private void ZeroOnePack(int cost, int weight) {
for (int v = V; v >= cost; v--) {
f[v] = Math.max(f[v], f[v - cost] + weight);
}
}
/**
* 完全背包
*
* @param cost
* 消耗容量
* @param weight
* 体积
* @return
*/
private void CompletePack(int cost, int weight) {
for (int v = cost; v <= V; v++) {
f[v] = Math.max(f[v], f[v - cost] + weight);
}
}
/**
* 多重背包
*
* @param cost
* @param weight
* @param amount
*/
private void MultiplePack(int cost, int weight, int amount) {
if (cost * amount >= V) {
CompletePack(cost, weight);
return;
}
int k = 1;
while (k < amount) {
ZeroOnePack(k * cost, k * weight);
amount = amount - k;
k = k * 2;
}
ZeroOnePack(amount * cost, amount * weight);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] cost = { 6, 3, 4, 2 };
int[] weight = { 30, 14, 16, 9 };
int[] amount = { 0, 1, 2, 3 };
Pack zo = new Pack();
/************01背包************/
zo.resetF();
for (int i = 0; i < N; i++) {
zo.ZeroOnePack(cost[i], weight[i]);
}
System.out.println(zo.f[V] + "");
/************完全背包 ************/
zo.resetF();
for (int i = 0; i < N; i++) {
zo.CompletePack(cost[i], weight[i]);
}
System.out.println(zo.f[V] + "");
/************多重背包************/
zo.resetF();
for (int i = 0; i < N; i++) {
zo.MultiplePack(cost[i], weight[i], amount[i]);
}
System.out.println(zo.f[V] + "");
}
}
同时还有一篇讲动态规划的文章推荐给大家: http://www.cnblogs.com/sdjl/articles/1274312.html
同时附录出文章中的C++代码:
#include<iostream>
#include<fstream>
using namespace std;
//http://www.cnblogs.com/sdjl/articles/1274312.html
const int max_n=100;//程序支持的最多金矿数
const int max_people=10000;//程序支持的最多人数
int n;//金矿数
int peopleTotal;//可以用于挖金子的人数
int peopleNeed[max_n];//每座金矿需要的人数
int gold[max_n];//每座金矿能够挖出来的金子数
int maxGold[max_people][max_n];//maxGold[i][j]保存了i个人挖前j个金矿能够得到的最大金子数,等于-1表示未知
//初始化数据
void init(){
ifstream inputFile("beibao.in");
inputFile>>peopleTotal>>n;
for(int i=0;i<n;i++){
inputFile>>peopleNeed[i]>>gold[i];
}
inputFile.close();
for(int i=0;i<=peopleTotal;i++){
for(int j=0;j<n;j++){
maxGold[i][j]=-1;//等于-1时表示未知
}
}
}
//获得在仅有people个人和前mineNum个金矿时能够得到的最大金子数
int GetMaxGold(int people,int mineNum){
int retMaxGold; //返回的最大金子数
if(maxGold[people][mineNum]!=-1){//如果曾经这个问题已经计算过
retMaxGold=maxGold[people][mineNum];//获得保存起来的值
}
else if(mineNum==0){//如果仅有一个金矿
if(people>peopleNeed[mineNum]){
retMaxGold=gold[mineNum];
}else{//否则一个矿也不能开采
retMaxGold=0;
}
}
else if(people>=peopleNeed[mineNum]){//人数够开采此矿
retMaxGold=max(GetMaxGold(people-peopleNeed[mineNum],mineNum-1)+gold[mineNum],GetMaxGold(people,mineNum-1));//两种情况,要么开采此矿剩下的人采其它矿;要么不开采此矿,所有人开采下一个矿.两者取最大值
}
else{//剩下的人无法开采此矿,直接开采下一个矿
retMaxGold=GetMaxGold(people,mineNum-1);
}
maxGold[people][mineNum]=retMaxGold;//记录下当前人数能采mineNum号矿的最大值
return retMaxGold;
}
int main(){
init();
cout<<GetMaxGold(peopleTotal,n-1);
//return 0;
}