0

I am a beginner in coding, I was doing a leetcode problem "121. Best Time to Buy and Sell Stock". I wrote a code that works pretty well but when I try to run it, it says Time Limit Exceeded. Looking at this code, this would be O(n) time complexity and for the space complexity it would be O(1). I have seen other solutions using a while loop (kadane's algorithm) and it runs perfectly.

l = 0
r = 1
maxx = 0
if len(prices) <= 1:
    return 0
while l <= r:
    profit = prices[r] - prices[l]
    if r != len(prices) - 1:
        r += 1
    elif l == len(prices) - 2:
        if maxx < profit:
            maxx = profit
        break
    else:
        l += 1
        r = l + 1

    if maxx < profit:
        maxx = profit

return maxx
DevitySire
  • 21
  • 2
  • 3
    Looking at the `else` part, it seems that the loop condition will remain true forever. –  Nov 28 '22 at 08:26
  • Ahh thats it, cant believe I overlooked that, thanks – DevitySire Nov 28 '22 at 08:29
  • Why is this O(n)? It looks quadratic. It looks like a complicated way of writing `for l in range(len(prices)-1): for r in range(l+1, len(prices)): ...` – Paul Hankin Nov 28 '22 at 08:29
  • 2
    Yes there are nested loops, it's just that you've written them in a weird way. `r` goes up to the end of the array, then when it gets there, `l` is incremented and `r` starts again from `l+1`. Your code iterates over all pairs `0<=l – Paul Hankin Nov 28 '22 at 08:35
  • You can run a test case with 100, 1000 and then 10000 elements in the input and see how long it takes. It'll be easy to tell that it's not linear. These coding websites are ok, but you have to learn to test your own code and not rely on feedback from the website. – Paul Hankin Nov 28 '22 at 08:39
  • The comment from @bbbbbbbbb is wrong, or partially wrong at least. Sure, the loop condition is always true, but you have a `break` when `l` and `r` reach the end of the array, so your code is functionally correct although of the wrong complexity. – Paul Hankin Nov 28 '22 at 08:42
  • To get linear time, as you go through the days, keep track of the cheapest price you could have bought the stock, and consider the profit from buying it at that price and selling it at the current price. There's no Kadane's algorithm or anything fancy, it's just maintaining a running value (ie: the minimum buy price) to avoid recomputing it each time. – Paul Hankin Nov 28 '22 at 08:52
  • I see, kadane's algorithm is different. Thank you for going into detail, its starting to make sense. – DevitySire Nov 28 '22 at 09:17
  • @PaulHankin: I meant "always true" as in - whenever the `else` is executed (as stated in my comment, btw). So in other words, this was not in order to say that the code is wrong, but rather, that the assumption of the code complexity being `O(n)` is wrong, which makes my comment equivalent to yours (so either it's right, or we're both wrong). –  Nov 28 '22 at 12:40

1 Answers1

0

Some observations:

  • Either l is updated and r is set to one more, or l is not updated and r does not diminish. This means that the while condition is always satisfied. The only way to exit the loop is via the break. This means the loop header could also have been written as while True:

  • The r index visits the same index multiple times, as r = l + 1 generally decreases its value The algorithm produces all possible pairs of [l, r] where l < r. There are n(n-1)/2 of those pairs, so the algorithm has a complexity of O(n²).

This complexity is not optimal -- as you know. We can get a "feel" of that inefficiency by realising the following: when we have found the optimal r for a given l, it makes no sense to look at lesser values of r when l has been increased. Surely that optimal r for that previous l is still going to be the optimal one for the next value of l. Only when that optimum was at l + 1 we need a "fresh" one. So your algorithm is wasting time on values of r that we already know cannot be optimal.

Similarly, when the price at l is greater than a previous one, there is no way that the optimal r (for that l) will improve the best profit we already had. So then there should be no reason at all to have r iterate over the rest of the array: it will be fruitless.

Optimal algorithm

You have already mentioned Kadane's algorithm (based on day-to-day price differences)

I find the following one-pass algorithm quite intuitive:

When at a certain index during that single pass, let's assume you have correctly identified the following, using only the part of the list that was already processed:

  1. The best profit in that list. If this sub list would be all there was, this would be the final answer.

  2. The minimum price in that list. This is a good candidate to buy and try to make even better profits when going further.

Then, when reading the next price from the list, there are the following cases:

  • Maybe that price is high enough to improve on the best profit (considering a buy at the minimum price we had so far, and selling at this current price): if so, we just have to update that best profit.

  • Maybe that price is lower than the minimum price we opted for. In that case we can forget about the previous minimum we had, since any future improvement we could get by using that previous minimum price would immediately be improved by taking the current price as buying price instead, so we don't need the previous minimum price anymore.

  • In all other cases we have a price that is non interesting, nor as buying price, nor as selling price.

After this step we are again sure we have determined the best profit for the range of prices that was processed, knowing also the minimum price in that range.

So by induction this process will give the correct answer.

Code:

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        maxprofit = 0
        minprice = prices[0]
        for price in prices:
            if price < minprice:
                minprice = price
            elif price - minprice > maxprofit:
                maxprofit = price - minprice  # buy at minprice & sell at price
                
        return maxprofit
trincot
  • 317,000
  • 35
  • 244
  • 286