股票问题

声明: 原创作品,转载请注明出处!


问题简述:给定你一组数,里面存的是一个月的股票每天的价格,现在你知道了股票每天的价格,问你在这一个月里,在哪天买入,哪天卖出,可以使你的收益最大?

问题来源:阿里电面


问题分析:

该问题的本质为从一组数中,找出两个数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


图二




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值