声明: 原创作品,转载请注明出处!
问题简述:给定你一组数,里面存的是一个月的股票每天的价格,现在你知道了股票每天的价格,问你在这一个月里,在哪天买入,哪天卖出,可以使你的收益最大?
问题来源:阿里电面
问题分析:
该问题的本质为从一组数中,找出两个数ai和aj(i <= j),使得 aj - ai 达到最大值。
首先,最容易想到的便是用双重循环做,外循环为在哪天买入,内循环为在哪天卖出,于是乎便有了如下算法:
// 解法一: (伪代码)
pair<int, int> Find(int* arr, int n)
{
int b, s, profit;
s = b = 0; // 第 1 天买入,第 1 天卖出
profit = 0;// 收益为0
for(int i = 0; i < n; ++i){
for(int j = i + 1; j < n;++j){
if(arr[j] - arr[i] > profit){
b = i;
s = j;
profit = arr[j] - arr[i];
}
}
}
return make_pair(b, s); // 第b天买入,第s天卖出
}
但是,显而易见的是,这样做的效率太低,时间复杂度为 O(n^2) 了,显然不是面试官想要得到的答案。
再仔细考虑一下,如果我们有一个数组 p[n], p[i] 表示在第 i 天卖出能达到的最大收益(因为 p[i] = a[i] - 买入的价格,显然 p[i] 取最大值是 a[i] - 目前找到的最小值),另置一个数组buy[n],b[i] 表示得到 p[i] 时是在哪天买入的,通过一次循环得到数组 p[i],然后再通过遍历一次 p[n] 找到 p[n] 中的最大值(不妨设 p[k] 为最大值),那么我们就可以得知应该在 buy[k] 天买入,在第 k 天卖出,最大收益为 p[k]。于是乎,便可以得到如下算法:
// 解法二: (伪代码)
pair<int, int> Find(int* arr, int n)
{
int b, s, min, k;
int* p = new int[n];
int* buy = new int[n];
buy[0] = 0; // 第一天买入
p[0] = 0; // 收益为0
min = 0; // 当前买入点
for(int i = 1; i < n; ++i){
if(arr[i] < arr[min]){
min = i; // min 用来记录当前最小值所在的位置
}
p[i] = arr[i] - arr[min];
buy[i] = min;
}
fot(int i = 1; i < n; ++i){
if(p[i] > p[k]){
k = i; // 记录收益最大是哪天卖出
}
}
b = buy[k];
s = k;
delete[] buy;
delete[] p;
return make_pair(b, s);
}
这样,我们利用了辅助数组之后,得到了一个 O(n) 时间复杂度, O(n) 空间复杂度的算法。(这是一种典型的以空间换取时间的做法。)
算法已经最优了吗?
我们不妨从以上的解答中寻找最优解的一些性质:假设在第 s 天卖出能达到的收益是最大的,那么它买入的时间点应该是迄今为止找到的最小值出现的点b。如果我们用一个变量记录当前找到的最小值点,然后再通过当前值减去最小值,看是否比目前的收益要大,如果收益更大,那说明我们在第 s 卖出是最划算的,然后更新目前的买入时间以及卖出时间,那么我们就可以不需要解法二中的两个辅助数组。因此,便有了如下的解:
// 解法三
pair<int, int> Find(int* arr, int n)
{
int i;
int p, b, s, min;
p = 0; // profit,完全可以优化掉,为了代码清晰可读,暂且留之
b = 0;
s = 0;
min = 0;
for(i = 0; i < n; ++i){
if(arr[i] < arr[min]){
min = i; // 记录当前最小值所在位置
}
if(arr[i] - arr[min] > p){
p = arr[i] - arr[min];
b = min; // 买入点
s = i; // 卖出点
}
}
return make_pair(b, s);
}
至此,我们得到了一个 时间复杂度为 O(n),空间复杂度为O(1)的解。
下面附上测试代码已经测试效果图(趋势图利用out.txt里的数据+ Excel 生成的)
#include<iostream>
#include<fstream>
#include<ctime>
#include<cstdlib>
#include<utility>
using namespace std;
#define MAX_N 31
void Generate(int* arr, int n, int m)
{
int i, sign;
srand((unsigned)time(NULL));
arr[0] = rand() % m;
for(i = 1; i < n; ++i){
sign = rand() % 2 ? 1 : -1;
arr[i] = arr[i - 1] + sign * (rand() % m) / 2;
}
}
void Print(int* arr, int n, ostream& out)
{
int i;
for(i = 0; i < n; ++i){
cout << arr[i] << '\t';
out << arr[i] << '\t';
}
}
int main(int argc, char* argv[])
{
int trends[MAX_N];
ofstream out("out.txt");
Generate(trends, MAX_N, 100);
cout << "Trends:\n";
Print(trends, MAX_N, out);
cout << endl;
pair<int, int> r = Find(trends, MAX_N);
cout << "Buy in: " << r.first + 1 << ". [" << trends[r.first] << "]" << endl;
cout << "Sell out: " << r.second + 1 << ". [" << trends[r.second] << "]" << endl;
cout << "Profit: " << trends[r.second] - trends[r.first] << endl;
return 0;
}
运行&效果图:
图1
图二