But it doesn't have to end here! Sign up for the 7-day coding interview crash course and you'll get a free Interview Cake problem every week.
Writing programming interview questions hasn't made me rich yet ... so I might give up and start trading Apple stocks all day instead.
First, I wanna know how much money I could have made yesterday if I'd been trading Apple stocks all day.
So I grabbed Apple's stock prices from yesterday and put them in a list called stock_prices, where:
So if the stock cost $500 at 10:30am, that means stock_prices = 500.
Write an efficient function that takes stock_prices and returns the best profit I could have made from one purchase and one sale of one share of Apple stock yesterday.
No "shorting"—you need to buy before you can sell. Also, you can't buy and sell in the same time step—at least 1 minute has to pass.
You can't just take the difference between the highest price and the lowest price, because the highest price might come before the lowest price. And you have to buy before you can sell.
What if the price goes down all day? In that case, the best profit will be negative.
You can do this in time and space!
To start, try writing an example value for stock_prices and finding the maximum profit "by hand." What's your process for figuring out the maximum profit?
The brute force approach would be to try every pair of times (treating the earlier time as the buy time and the later time as the sell time) and see which one is higher.
But that will take time, since we have two nested loops—for every time, we're going through every other time. Also, it's not correct: we won't ever report a negative profit! Can we do better?
Well, we’re doing a lot of extra work. We’re looking at every pair twice. We know we have to buy before we sell, so in our inner for loop we could just look at every price after the price in our outer for loop.
That could look like this:
What’s our runtime now?
Well, our outer for loop goes through all the times and prices, but our inner for loop goes through one fewer price each time. So our total number of steps is the sum n + (n - 1) + (n - 2) ... + 2 + 1, which is still time.
We can do better!
If we're going to do better than , we're probably going to do it in either or . comes up in sorting and searching algorithms where we're recursively cutting the list in half. It's not obvious that we can save time by cutting the list in half here. Let's first see how well we can do by looping through the list only once.
Since we're trying to loop through the list once, let's use a greedy approach, where we keep a running max_profit until we reach the end. We'll start our max_profit at $0. As we're iterating, how do we know if we've found a new max_profit?
At each iteration, our max_profit is either:
How do we know when we have case (2)?
The max profit we can get by selling at the current_price is simply the difference between the current_price and the min_price from earlier in the day. If this difference is greater than the current max_profit, we have a new max_profit.
So for every price, we’ll need to:
Here’s one possible solution:
We’re finding the max profit with one pass and constant space!
Are we done? Let’s think about some edge cases. What if the price stays the same? What if the price goes down all day?
If the price doesn't change, the max possible profit is 0. Our function will correctly return that. So we're good.
But if the value goes down all day, we’re in trouble. Our function would return 0, but there’s no way we could break even if the price always goes down.
How can we handle this?
Well, what are our options? Leaving our function as it is and just returning zero is not a reasonable option—we wouldn't know if our best profit was negative or actually zero, so we'd be losing information. Two reasonable options could be:
In this case, it’s probably best to go with option (1). The advantages of returning a negative profit are:
How can we adjust our function to return a negative profit if we can only lose money? Initializing max_profit to 0 won’t work...
Well, we started our min_price at the first price, so let’s start our max_profit at the first profit we could get—if we buy at the first time and sell at the second time.
But we have the potential for an index out of bounds error here, if stock_prices has fewer than 2 prices.
We do want to raise an exception in that case, since profit requires buying and selling, which we can't do with less than 2 prices. So, let's explicitly check for this case and handle it:
Ok, does that work?
No! max_profit is still always 0. What’s happening?
If the price always goes down, min_price is always set to the current_price. So current_price - min_price comes out to 0, which of course will always be greater than a negative profit.
When we’re calculating the max_profit, we need to make sure we never have a case where we try both buying and selling stocks at the current_price.
To make sure we’re always buying at an earlier price, never the current_price, let’s switch the order around so we calculate max_profit before we update min_price.
We'll also need to pay special attention to time 0. Make sure we don't try to buy and sell at time 0.
We’ll greedily walk through the list to track the max profit and lowest price so far.
For every price, we check if:
To start, we initialize:
We decided to return a negative profit if the price decreases all day and we can't make any money. We could have raised an exception instead, but returning the negative profit is cleaner, makes our function less opinionated, and ensures we don't lose information.
time and space. We only loop through the list once.
This one's a good example of the greedy approach in action. Greedy approaches are great because they're fast (usually just one pass through the input). But they don't work for every problem.
How do you know if a problem will lend itself to a greedy approach? Best bet is to try it out and see if it works. Trying out a greedy approach should be one of the first ways you try to break down a new question.
To try it on a new problem, start by asking yourself:
"Suppose we could come up with the answer in one pass through the input, by simply updating the 'best answer so far' as we went. What additional values would we need to keep updated as we looked at each item in our input, in order to be able to update the 'best answer so far' in constant time?"
In this case:
The "best answer so far" is, of course, the max profit that we can get based on the prices we've seen so far.
The "additional value" is the minimum price we've seen so far. If we keep that updated, we can use it to calculate the new max profit so far in constant time. The max profit is the larger of:
Try applying this greedy methodology to future questions.
Powered by qualified.io