导语
问题是这样的:
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。
注意: 你不能在买入股票前卖出股票。
例如:输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 =
6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
思路
你当然希望 低价买进,高价卖出,这样便可以最大化利益。但遗憾的是,在一段给定时期内,可能无法做到。比如——最高价在前,最低价在后
1. 暴力求解方法
我们可以很容易地设计出一个暴力方法来求解问题。简单尝试每对可能的买卖日期组合,只要卖出日期在买入日期之后就可以。很明显需要2层嵌套的循环来遍历所有组合,因此时间复杂度O(n2)
2. 问题变换 : 最大子数组
为了设计出一个运行时间更快的算法。我们从不同角度来看待输入数据。我们的目的是寻找一段日期,使得从第一天到最后一天的股票价格增量最大。因此,我们不在从每日价格的角度是看待数据。而是看每日价格变化。第i天的价格定义为第i天和第i-1天的价格,那么问题就转换为寻找 股票数组的最大子数组问题
咋一看,这种变化对问题求解并没有陌生帮助。
虽然计算一个子数组之和所需要的时间是线性的,但计算所有子数组时,我们可以重新组织新的计算方式。从而利用之前计算出的子数组和来计算当前子数组的和。时间仍然是O(n2)
2.1 分治法
我们考虑如何用分治法来解决最大子数组问题。使用分治法意味着忙完要将子数组分为俩个规模尽量相等的的子数组。也就是找到中央位置。比如mid,然后考虑求解 A[low , mid] 和 A[mid + 1 , high ] ,任何连续子数组A[i , j ] 都必然是以下三种情况.
- 完全位于子数组A[low , high ] 因此 low <= i <= j <= high
- 完全位于子数组A[mid + 1 , high ] , 因此 mid < i <= j <= high
- 跨过了中点,因此low<=i<mid<j <=high
跨过中点的最大子数组
function find_max_crossing_subArray(arr , low , mid , high) {
let sum1= 0 ;
let left_sum = -Infinity ;
for(let i = mid ; i >= low ; i--) {
sum1 = sum1 + arr[i] ;
if(sum1 > left_sum ) {
left_sum = sum1 ;
}
}
let right_sum = -Infinity ;
let sum2 = 0 ;
for(let j = mid + 1 ; j <= high ; j++ ) {
sum2 = sum2 + arr[j] ;
if(sum2 > right_sum ) {
right_sum = sum2 ;
}
}
//返回最大子序列的 最大值
return left_sum + right_sum ;
}
可知道这个函数的时间复杂度是O(n)
现在我们就可以设计一个求解最大子数组的方法了。
function find_maximun_subArray (arr , low , high) {
// 如果数组中只有一个元素 , 则返回那个元素
if( low === high ) return arr[low] ;
else {
let mid = Math.floor((low + high)/2) ;
var left_sum =
find_maximun_subArray(arr , low , mid ) ;
var right_sum =
find_maximun_subArray(arr , mid + 1 , high) ;
var cross_sum =
find_max_crossing_subArray(arr , low , mid , high) ;
} ;
return Math.max(left_sum , Math.max(right_sum , cross_sum)) ;
}
总结:分治法的时间复杂度为 O(nlogn)