LeetCode> 121. 买卖股票的最佳时机(三种解法)
题目
对于非负整数 X 而言,X 的数组形式是每位数字按从左到右的顺序形成的数组。例如,如果 X = 1231,那么其数组形式为 [1,2,3,1]。
给定非负整数 X 的数组形式 A,返回整数 X+K 的数组形式。
示例 1:
输入:A = [1,2,0,0], K = 34
输出:[1,2,3,4]
解释:1200 + 34 = 1234
示例 2:
输入:A = [2,7,4], K = 181
输出:[4,5,5]
解释:274 + 181 = 455
示例 3:
输入:A = [2,1,5], K = 806
输出:[1,0,2,1]
解释:215 + 806 = 1021
示例 4:
输入:A = [9,9,9,9,9,9,9,9,9,9], K = 1
输出:[1,0,0,0,0,0,0,0,0,0,0]
解释:9999999999 + 1 = 10000000000
提示:
1 <= A.length <= 10000
0 <= A[i] <= 9
0 <= K <= 10000
如果 A.length > 1,那么 A[0] != 0
解题思路
方法一:暴力搜索
T(n)=O(n^2)
遍历价格数组, 求解以当天为卖出日期的最大值. 得到一个针对每天卖出股票的利润最大值数组, 数组的最大值, 即为利润最大值
/**
* 方法一: 暴力搜索, T(n)=O(n^2)
* 遍历价格数组, 求解以当天为卖出日期的最大值. 得到一个针对每天卖出股票的利润最大值数组, 数组的最大值, 即为利润最大值
*/
public int maxProfit(int[] prices) {
if (prices == null || prices.length < 1) return 0;
int max = 0;
for (int i = 1; i < prices.length; i++) {
for (int j = 0; j < i && j < prices.length; j++) {
int profit = prices[i] - prices[j];
if (profit > max) max = profit;
}
}
return max;
}
方法二:动态规划
T(n) = O(n)
先求出到第i天时利润最大值dp[i], 到最后一天时, 利润最大值就是dp[i]。
dp[i]的求法, 分为两种可能情况:
1.第i天有卖出, 也就是0..i-1天中股价最小时买入, 第i天卖出;
2.第i天没有卖出, 利润最大值同第i-1天。
/**
* 方法二: 动态规划, T(n) = O(n)
* 先求出到第i天时利润最大值dp[i], 到最后一天时, 利润最大值就是dp[i]
* dp[i]的求法, 分为两种可能情况:
* 1.第i天有卖出, 也就是0..i-1天中股价最小时买入, 第i天卖出;
* 2.第i天没有卖出, 利润最大值同第i-1天.
*/
public int maxProfit(int[] prices) {
if (prices == null || prices.length < 1) return 0;
int min = prices[0];
int[] dp = new int[prices.length];
dp[0] = 0;
for (int i = 1; i < dp.length; i++) {
if (prices[i-1] < min) min = prices[i-1];
dp[i] = Math.max(dp[i-1], prices[i] - min);
}
return dp[dp.length-1];
}
方法三:分治法
T(n) = O(nlgn)
先求出每天股票变化值(prices[i]-prices[i-1]), 问题转化为求解(和)最大连续子数组
数组A[low..high]中的最大连续子数组A[left..right]分为三种情况:
- 子数组全部落到左半部, 即low <= left < right <= mid;
- 子数组全部落到右半部, 即mid < left < right <= high;
- 子数组跨越中点, 即low <= left <= mid < right <= high;
情形1和2, 是求解最大连续子数组的子问题, 可以递归求解. 情形3有特殊限制, 需要专门讨论如何处理
/**
* 方法三: 分治法, T(n) = O(nlgn)
* 先求出每天股票变化值(prices[i]-prices[i-1]), 问题转化为求解(和)最大连续子数组
* 数组A[low..high]中的最大连续子数组A[left..right]分为三种情况:
* 1. 子数组全部落到左半部, 即low <= left < right <= mid;
* 2. 子数组全部落到右半部, 即mid < left < right <= high;
* 3. 子数组跨越中点, 即low <= left <= mid < right <= high;
* 情形1和2, 是求解最大连续子数组的子问题, 可以递归求解. 情形3有特殊限制, 需要专门讨论如何处理
*/
public int maxProfit(int[] prices) {
if (prices == null || prices.length < 1) return 0;
for (int i = prices.length-1; i > 0; i--) {
prices[i] = prices[i] - prices[i-1];
}
prices[0] = 0;
int[] res = findMaximumSubArray(prices, 0, prices.length-1);
return res[2];
}
public int[] findMaxCrossingSubArray(int[] A, int low, int mid, int high) {
int leftSum = Integer.MIN_VALUE;
int maxLeft = mid;
int sum = 0;
for (int i = mid; i >= low ; i--) {
sum += A[i];
if (sum > leftSum) {
leftSum = sum;
maxLeft = i;
}
}
int rightSum = Integer.MIN_VALUE;
int maxRight = mid;
sum = 0;
for (int j = mid+1; j <= high; j++) {
sum += A[j];
if (sum > rightSum) {
rightSum = sum;
maxRight = j;
}
}
return new int[]{maxLeft, maxRight, (leftSum + rightSum)};
}
public int[] findMaximumSubArray(int[] A, int low, int high) {
if (low == high) {
return new int[]{low, high, A[low]};
}
else {
int mid = (low + high) / 2;
int[] leftRes = findMaximumSubArray(A, low, mid);
int[] rightRes = findMaximumSubArray(A, mid+1, high);
int[] crossRes = findMaxCrossingSubArray(A, low, mid, high);
if (leftRes[2] >= rightRes[2] && leftRes[2] >= crossRes[2])
return leftRes;
else if(rightRes[2] >= leftRes[2] && rightRes[2] >= crossRes[2])
return rightRes;
else return crossRes;
}
}