views:

490

answers:

3

Got this question in an interview today, and its optimized solution stopped me cold (which blows, because I really wanted to work for this company...)

Given a single array of real values, each of which represents the stock value of a company after an arbitrary period of time, find the best buy price and its corresponding best sell price (buy low, sell high).

To illustrate with an example, let's take the stock ticker of Company Z:

55.39 109.23 48.29 81.59 105.53 94.45 12.24

Important to note is the fact that the array is "sorted" temporally - i.e. as time passes, values are appended to the right end of the array. Thus, our buy value will be (has to be) to the left of our sell value.

(in the above example, the ideal solution is to buy at 48.29 and sell at 105.53)

I came up with the naive solution easily enough with O(n2) complexity (implemented in java):

// returns a 2-element array: first element is the index in the argument array
// of the best buying price, and the second element is the index of the best
// selling price which, collectively, maximize the trading return
//
// if there is no favorable trading (e.g. prices monotonically fall), null is returned
public int[] maximizeReturn(ArrayList<Double> prices) {
  int [] retval = new int[2];
  int BUY = 0, SELL = 1;
  retval[BUY] = retval[SELL] = -1; // indices of buy and sell prices, respectively

  for (int i = 0; i < prices.size(); i++) {
    for (int j = i + 1; j < prices.size(); j++) {
      double difference = prices.get(j).doubleValue() - 
                          prices.get(i).doubleValue();

      if (difference > 0.0) {
        if (retval[BUY] < 0 || difference > prices.get(retval[SELL]).doubleValue() - 
                                            prices.get(retval[BUY]).doubleValue()) {
          retval[BUY] = i;
          retval[SELL] = j;
        }
      }
    }
  }
  return (retval[BUY] > 0 ? retval : null);
}

Here's where I screwed up: there's a linear time O(n) solution, and I completely bombed in trying to figure it out (yeah, I know, FAIL). Does anyone know how to implement the linear time solution? (any language you're comfortable with) Thanks!

Edit

I suppose, for anyone interested, I just received word today that I didn't get the job for which I interviewed where they asked me this question. :(

+4  A: 

Here's an attempt (C++). Basically everytime I track a new top, I try to see if thats the best profit thusfar. I know that the "bottom" must have been discovered earlier. At that point I remember the top, bottom, and the current max profit. If a new bottom is discovered later, its AFTER the current top, so we must reset top and see if a slightly lower "top" can yield better profit.

#include <iostream>

int main()
{

    double REALLY_BIG_NO = 1e99;
    double bottom = REALLY_BIG_NO; // arbirtrary large number
    double currBestBuy = 0.0;
    double top = 0.0;
    double currBestSell = 0.0;
    double profit = 0.0;

    // array of prices
    double prices[] = {10.50, 55.39, 109.23, 48.29, 81.59, 105.53, 94.45, 12.24, 152.0, 2, 170.0};
    int numPrices = 10;// number of prices

    for (int i = 0; i < numPrices; ++i)
    {
         if (prices[i] < bottom)
         {
            bottom = prices[i];
            // reset the search on a new bottom
            top = 0.0;
         }
         else if (prices[i] > top)
         {
            top = prices[i];
           // calculate profit
            double potentialProfit = (top - bottom);
            if (potentialProfit > profit &&
                bottom != REALLY_BIG_NO)
            {
                profit = potentialProfit;
                currBestSell = top;
                currBestBuy = bottom;
            }
         }
    }

    std::cout << "Best Buy: " << currBestBuy << "Best Sell: " << currBestSell << std::endl;
}

So far I've played around with a bunch of different input sets, and so far I haven't had any problems... (let me know if you test this and see anything wrong)

I highly recommend using Austin Salonen's updated answer to this question and adapting it to your language.

Doug T.
I was inching close to a solution like this one as I bumbled along; I had five variables set up exactly as you do. Unfortunately I started doing some crazy value swapping and pretty much went off the deep end from there. =/
Magsol
I rebuilt my algo and it's the same as yours now... Different language and some really good comments so I'll leave it up.
Austin Salonen
+4  A: 

In C#:

static void Main(string[] args)
{
    double[] values = new double[7]{55.39, 109.23, 48.29, 81.59, 105.53, 94.45, 12.24};

    double max = double.MinValue, maxDiff = double.MinValue, diff = 0;

    for (int i = 1; i < values.Length; i++)
    {
        if (values[i] > values[i - 1])
        {
            //trending upward, append to existing differential
            diff += values[i] - values[i - 1];
        }
        else
        {
            //trending downward, reset the diff
            diff = 0;
        }

        if (diff > maxDiff)
        {
            maxDiff = diff;
            max = values[i];
        }
    }

    Console.WriteLine("Buy at {0}; Sell at {1}", max - maxDiff, max);
}

EDIT: New algo based on @Joe's failing test case -- Nice Catch BTW! It's also the same answer as @Doug T's now...

static void Main(string[] args)
{
    double[] values = new double[8] { 55.39, 109.23, 48.29, 81.59, 81.58, 105.53, 94.45, 12.24 };

    double max = double.MinValue, maxDiff = double.MinValue, diff = 0;
    double bottom = values[0];

    for (int i = 1; i < values.Length; i++)
    {
        diff += values[i] - values[i - 1];

        if (diff > maxDiff)
        {
            maxDiff = diff;
            max = values[i];
        }

        if (values[i] < bottom)
        {
            bottom = values[i];
            diff = 0;
        }
    }

    Console.WriteLine("Buy at {0}; Sell at {1}", max - maxDiff, max);
}
Austin Salonen
+1 for the more elegant answer
Doug T.
Austin Salonen
I really like this; the idea of incrementing the differential hadn't occurred to me. Very elegant!
Magsol
This seems to fail for the input 55.39, 109.23, 48.29, 81.59, 81.58, 105.53, 94.45, 12.24 ? Best is still to buy at 48.29 and sell at 105.53 (57.24 profit), but it says to buy at 55.39 and sell at 109.23 (53.84 profit)
Joe
Yup, small dips confuse this algo.
Eric H.
Actually yes this solution seems to have a flaw whereby if you trend downward then immediately back up again, but the bottom was before you trended downward, your diff is reset and you won't see the opportunity...
Doug T.
Yeah I guess you can't just track diffs because you have to know at any given time where the global minimum is... damn I thought you were onto something really a lot more elegant than my solution. Maybe you are, and there's a quick fix?
Doug T.
Nice catch @Joe.
Austin Salonen
I left the original code in the answer so the comments make sense.
Austin Salonen
Very nice, glad to see you could find an answer.
Doug T.
A: 

I really have to point out as an interview question expecting you to solve it as O(n) is borderline absurd. Interview questions are meant to prove you can solve a problem, which you were able to solve it. The fact you solved it in O(N^2) vs O(N) should be irrelevant. If a company would pass over hiring you for not solving this in O(N) that's probably not a company you would have wanted to work at anyway.

Chris Marisic
"The fact you solved it in O(N^2) vs O(N) should be irrelevant." - I really hope you are right on this one :)
Magsol
well as an interviewer, its often useful to push interviewees. All else being equal, if only 1 position is open, given the choice between the O(n) solver and the O(n^2) solver, I'll take the O(n) solver. That being said, I'm glad when I interview someone these days that knows O(n) from O(n^2) so you'd probably get the job where I work!
Doug T.
@Doug, the issue is about solving the problem first. Unless we're talking N into *illions for a modern computer the difference from linear and binomial time should be negligible. This also goes with avoid early optimization, if they asked a question that could be solved with recursion easily is it better to use the elegant method or to spend the time to write it so it can be done in a loop instead of recursively before it's needed? Optimization can always be done later.
Chris Marisic
Having been the interviewer, I'd say that posing this question is a great tool: 1) if they can't code even the O(n^2) solution, they're not a programmer. 2) If they've seen the O(n) solution, that just means they've done a lot of reading (it's really an obscure 'AHA' kind of question). 3) If they haven't, then walking through the thought process of trying to find it should be very illuminating.
Eric H.