Gas Station - Greedy - Leetcode 134 - Python

Поделиться
HTML-код
  • Опубликовано: 26 ноя 2024

Комментарии • 194

  • @ZQutui
    @ZQutui 3 года назад +190

    Actually, the reason why it works is simple, and it happens because of two factors.
    First, if you moved to some value, and your total sum is greater than zero, then it means, that previous values did bring some value to the outcome. For example, we have gas = [2,3,0] and cost = [0,0,5]. If we take just solely value 3 without 2, it wouldn't be enough to pass the last station, but previous values definitely bring some value to the outcome.
    Second, if we know, that there's definitely has to be a solution. Then, we may assume, that it has to be the smallest possible value, as I said before it may bring the most value to the result

    • @NeetCode
      @NeetCode  3 года назад +17

      Yes, nicely explained!

    • @arpanbanejee5143
      @arpanbanejee5143 3 года назад +18

      could not understand the last line... : (

    • @arpanbanejee5143
      @arpanbanejee5143 3 года назад +135

      Yes I got it! I will try to explain it in my own way-- please be patient and read it, hope you will get the intuition behind the algorithm.
      There are 4 parts to it-
      Part 1- sum of gas array>=sum of cost array--->
      very intuitive, we should always have enough gas.
      Part 2- we are keeping a total+=gas[i]-cost[i] for each i, and whenever it is
      It means we ran out of gas if we started at some point which was curr pos of i.
      Now think, why will this new start lie ahead of curr pos i, not anywhere before it, you could think, we started from point A------>B(total till +ve)------->C(totalY(-ve)--->A(+ve)---->B(+ve)---->C(+ve), where C is the end of the array, our start(which is also the ans) would be A.
      Why not B? why not C?
      It is because we moved from A to B with some +ve value or atleast 0, whereas if we started from B we would have had only the value of B so earlier point added some value to our total, so its more favorable to help us reach the ans, hence earliest point is always better.
      Part 4-- Why we just stop at point C and don t complete the cycle and check.
      It is because from Part 1 we would have already identified that if the given set of inputs will have an ans, so if we have reached to Part 3 it means we surely have an ans, and it is mentioned in the question that there is only one valid ans, so we will always choose the most favorable ans-- which is also the fundamental idea of Greedy Algorithims.
      Hope this clears things out!

    • @anujkumarpatel2686
      @anujkumarpatel2686 2 года назад +10

      is it somewhat similar to Kadane's Algorithm?

    • @jingyuchang1885
      @jingyuchang1885 2 года назад +2

      @@arpanbanejee5143 This is great! Thanks!

  • @rayahhhmed
    @rayahhhmed 2 года назад +259

    lowkey bro, firstly, i love ur vids as usual but when u started off by confessing that this problem was hard for u too and didnt act like some of those "know all" kind of people,
    just shows how down to earth u are and how much we can relate to even someone as good at dsa problems as u.
    please keep it up!

  • @waliddib5291
    @waliddib5291 2 года назад +318

    with the current gas prices, we can just return -1 regardless.

  • @arpanbanejee5143
    @arpanbanejee5143 3 года назад +123

    I will try to explain it in my own way-- please be patient and read it, hope you will get the intuition behind the algorithm.
    There are 4 parts to it-
    Part 1- sum of gas array>=sum of cost array--->
    very intuitive, we should always have enough gas.
    Part 2- we are keeping a total+=gas[i]-cost[i] for each i, and whenever it is
    It means we ran out of gas if we started at some point which was curr pos of i.
    Now think, why will this new start lie ahead of curr pos i, not anywhere before it, you could think, we started from point A------>B(total till +ve)------->C(totalY(-ve)--->A(+ve)---->B(+ve)---->C(+ve), where C is the end of the array, our start(which is also the ans) would be A.
    Why not B? why not C?
    It is because we moved from A to B with some +ve value or atleast 0, whereas if we started from B we would have had only the value of B so earlier point added some value to our total, so its more favorable to help us reach the ans, hence earliest point is always better.
    Part 4-- Why we just stop at point C and don t complete the cycle and check.
    It is because from Part 1 we would have already identified that if the given set of inputs will have an ans, so if we have reached to Part 3 it means we surely have an ans, and it is mentioned in the question that there is only one valid ans, so we will always choose the most favorable ans-- which is also the fundamental idea of Greedy Algorithims. There is also a mathematical proof for this, that if we got a start point given our total gas >=total cost , we will be able to reach back to that point with just enough gas.
    Hope this clears things out!

    • @kireeti93
      @kireeti93 2 года назад +6

      Best explanation found so far.

    • @AchyutJagini
      @AchyutJagini 2 года назад +1

      Thank you!!

    • @melissac4872
      @melissac4872 2 года назад +3

      The key is "t if we got a start point given our total gas >=total cost , we will be able to reach back to that point with just enough gas. "

    • @rajsekhardutta8891
      @rajsekhardutta8891 Год назад +1

      Thank you so much!! Best explanation ever.

    • @rasi9441
      @rasi9441 Год назад +5

      I had trouble understanding part2:
      I finally made peace thinking this way.
      assume you were coming from A-->B-->C with certain force, but you were not able to tackle the obstacle at C because C was big enough, even with all previous value combined. Hence if you are starting from B, you essentially reduce the force because you are selecting a smaller chain than the previous chain.

  • @RanjuRao
    @RanjuRao 3 года назад +47

    It was helpful ! Reading these problem statements in leetcode is overwhelming , coming here and looking at the explanations for the same relaxes me honestly :D

  • @PallNPrash
    @PallNPrash 2 года назад +8

    I too had difficulty understanding the solution in leetcode. Felt good seeing you mention the same. Great videos, NeetCode...You are my first youtube video for problems i have trouble understanding. Much appreciated!!

  • @vedantsharma5876
    @vedantsharma5876 2 года назад +35

    The solution would click if you try to apply the pattern as in Kadane's Algorithm.
    Try to build a solution from the starting index, once you are certain that it can no longer be an answer, forget everything and consider the next index.

    • @mostinho7
      @mostinho7 Год назад +4

      But that would be O(n^2) right

    • @Mickey_ej
      @Mickey_ej 10 месяцев назад +2

      @@mostinho7 no, that's the point of "forgetting everything in between" and consider the next index, if you do that we wont be doing repetitive work

  • @jamesbotwina8744
    @jamesbotwina8744 2 года назад +9

    I would add that when we reset the position, we would not want just increment the res, but instead set it to i + 1. We already know that since we are only adding net values that keep the total positive, as soon as we encounter a value that makes the total negative, it must be so large that it none of the previous nets's indexes could can out be a starting value.

    • @mostinho7
      @mostinho7 Год назад +2

      This comment made the solution click for me thank you

  • @shaiyonhariri6115
    @shaiyonhariri6115 9 месяцев назад +3

    This is the first actually challenging leetcode medium I've been able to figure out the efficient solution for perfectly myself in less than 30 minutes. I've done 50 of the Neetcode 150. Thank you.

  • @prayag3254
    @prayag3254 3 года назад +11

    This might help to understand why the solution works.
    Now, we have calculated that the total sum of differences is not negative, i.e Sum(0, N) >= 0.
    -> Let's say we found our starting point 'S'.
    -> Let us also assume that there was an index 'k' which our solution didn't reach.
    We can express the total sum as, Sum(0, N) = Sum(0, k) + Sum(k+1, S-1) + Sum(S, N).
    Sum(k+1, S-1) has to be negative, otherwise the starting point would be before S.
    Now, As per our assumption we can't reach 'k'.
    Therefore, Sum(S, N) + Sum(0, k) < 0.
    But, if Sum(S, N) + Sum(0, k) < 0 and Sum(k+1 , S-1) < 0, then S(0, N) should also be negative, which we have calculated to be positive.
    Therefore, our assumption was wrong.
    Hence, there is no point 'k' which our solution cannot reach.

    • @kishanrai5388
      @kishanrai5388 2 года назад +1

      Thanks for the explanation.

    • @Eeeason-c5y
      @Eeeason-c5y Год назад +1

      Beautiful explanation

    • @davidespinosa1910
      @davidespinosa1910 8 месяцев назад +2

      Thanks, that's the actual proof.
      If Sum(0, k) + Sum(k+1, S-1) + Sum(S, N) is positive,
      and the second term Sum(k+1, S-1) is negative,
      then the sum of the first and third Sum(0, k) + Sum(S, N) must be positive. QED.
      In other words, A+B+C positive and B negative implies A+C positive.
      It's intuitive that B is negative, but IMO it's not entirely trivial to prove it.
      See the comment by @purnawirmanhuang5145 for an alternative algorithm.

    • @WentingYu-im5xk
      @WentingYu-im5xk 5 месяцев назад

      @@davidespinosa1910 omg this this amazing thank you

  • @theSilentPsycho
    @theSilentPsycho 2 года назад +16

    Similar to Kadane's Algorithm. In Kadane's we find the ending index of the maximum sum subarray. Here we have to find the starting index of that maximum sum subarray.

  • @odysy5179
    @odysy5179 4 месяца назад +5

    A Simple Proof:
    1. We are guaranteed that there is one unique solution.
    2. The solution must wrap around to the start, and therefore must reach the end of the array at some point.
    3. The first index that can reach the end of the array will always result in a higher gas total by the end of the array than any index after it that can reach the end of the array.
    4. Therefore, because of conditions 1, 2, and 3, the solution must be the first index that can reach the end of the array.

    • @BlackLightning73
      @BlackLightning73 3 месяца назад +2

      Ah this made it click for me. Basically at our first point where we can reach the end with non-zero gas at every stop, we know for sure there is no solution before this point. If there was another solution after this point, our current index can only be better, so there must be two solutions which is not possible. Since we know there must be a solution, assuming we've already checked sum(gas) > sum(gas), and we know there can be no answer preceding the index and no answer after the index, the first index must be the solution. I think the key here is the unique solution constraint.

  • @rle1087
    @rle1087 2 года назад +13

    Here's a quick proof I attempted regarding why we do not need to loop around!
    By implementation of our algorithm, we may assume that upon termination:
    1. SUM(gas) >= SUM(costs)
    => SUM(gas) - SUM(costs) >= 0
    => SUM(gas - costs) = SUM(diffs) >=0
    => SUM(diffs) >=0
    2.
    a) i is the first index from which we were able to reach the end of the list with a non-negative total.
    SUM_i_n(diffs) >= 0 iff ABS(SUM_i_n(diffs)) = SUM_i_n(diffs)
    b) Equivalently, there is no non-negative total prior to i.
    SUM_0_i(diffs) < 0 iff ABS(SUM_0_i(diffs)) = -SUM_0_i(diffs)
    We do NOT need to loop around iff the total from i is greater than equal to the total up to i.
    That is, we want to prove:
    ABS(SUM_i_n(diffs)) >= ABS(SUM_0_i(diffs))
    Now the proof.
    SUM(diffs) >= 0 [by assumption 1]
    SUM_0_i(diffs) + SUM_i_n(diffs) >= 0
    SUM_i_n(diffs) >= -SUM_0_i(diffs)
    ABS(SUM_i_n(diffs)) >= ABS(SUM_0_i(diffs)) [by assumptions 2.a) and 2.b)]
    ^so, we have proven that based on the assumptions of our algorithm (our termination condition of i, early return when we precompute diffs), we do not need to loop around.

  • @ASMReading_
    @ASMReading_ Год назад +1

    I took a similar approach but one I feel is slightly more intuitive. I would consider this a sliding window approach. If anyone's having trouble with this problem, try out this version.
    First, create an array called dif, where at each point, dif[i] = gas[i] - cost[i]. What this array tells us is how it affects our gas tank when we're at position i and moving to position i+1. If we see a positive value in dif[i], we know we gain a surplus of gas (because we filled up more than we spent moving forward). If we see a negative value, we know we have a net loss of gas. And if we see a 0, we break even moving forward from i to i+1, and it doesn't impact our gas tank to make this move.
    At this point we can start our sliding window approach. Two pointers, start and end, both point to the first element (i.e. start = end = 0). We also keep track of how much gas we have in our tank as we progress on the road trip; we hold this in a variable called tank, initially set to tank=0 because we haven't filled up any gas just yet.
    The general idea of the loop is: start marks the start of our road trip, end marks the end. Tank holds the total amount of gas we currently have on a trip from start to end. When we can push end forward, we do; when we can't due to lack of gas, we push start backwards, seeking some extra gas. Let's get more specific with how and when we push forward and back.
    Pushing Forward
    When exactly can we push the end pointer forward? When tank + dif[end] >= 0. Why? Because this means that all the gas we have in our tank, when also considering the net cost of moving forward by one position (dif[end]), is enough to move us forward without dipping our tank into the negatives. In other words we have enough gas to progress to the next station on this current trip. So in these cases when we're able to extend the trip, we add the value of dif[end] to the tank and then increment end by 1. Then we try to move forward again... until we run out of gas.
    Pushing Back
    We do this when we run out of gas, i.e. where tank + dif[end] < 0. These are cases where, for example, tank was positive but not high enough to make up for the net loss of moving to the next station. So, in such cases, we seek extra gas by moving the start of our road trip backwards. Every time we move start backwards (i.e. decrement start by 1), we then adjust the value of tank by the new dif[start], because this net gain/loss is now part of our overall road trip and affects our gas tank. It's possible that when we move back one spot, we actually lose gas and the tank dips into the negatives. But we just keep pushing start backwards and updating the value of our tank (adding every new dif[start] to tank), until we reach a value where tank + dif[end] >= 0 and we can begin pushing the end pointer forward again.
    End Conditions
    Inevitably, because our only two operations are bringing start backwards and pushing end forwards, start will equal end. If we arrive at this condition by pushing end forward, we know the trip was possible, because we only move end forward when possible. In such a case, tank >= 0, because the last step would have ensured tank + dif[end] >= 0, and then set tank += dif[end]. If we arrive at our end condition from moving start backwards, it means we were seeking more gas. If tank >= 0, it means we found the gas we were looking for, and the trip is possible. If tank < 0, it means we never made up for the necessary gas and the trip is impossible. In short, after reaching the point where start = end, we return the index of start if tank >= 0, and -1 if tank < 0.
    Why does it work?
    I had two concerns with this algorithm, both of which can be dismissed. The first is: when moving start backwards, how do we know it's safe and that we don't accidentally incorporate an impossible path into our route? That is, since we move start backwards and add dif[start] each time, and some of those dif[start] values will be negative, isn't it possible that a section is impossible to pass? This was a little hard to wrap my head around so I may not explain it well, but pretty much it comes down to the fact that we maintain an accurate value in tank for each adjusted start position. When we encounter negative values at dif[start], they push our tank further into the negatives. The only way we can return to pushing end forward is if we find enough positive values at each new dif[start] that we make up for the new loss. For example, if we push start back by 1 and find dif[start] = -50, the only way we'll start pushing end forward again is if we make up for that 50, say by moving start backwards 50 more times and each time finding dif[start] = +1. Positions with a net loss become new blocks until enough surplus gas is found in prior positions to allow us to move past them too.
    Second potential issue that can be dismissed: we start by pushing end forward, then as needed pushing start backwards. What happens if the end and start pointers meet at some index k, but the real starting position is somewhere between index 0 and k? We end the code when start and end meet, and start never has a chance to pass end and inspect those earlier indices because it has essentially been moving backwards from the end of the list the whole time. The reason this is not an issue is because we are told that when a solution exists, there is only one unique solution. By the way this algorithm works, end is only pushed forward if it can be and if the resulting tank value is >= 0. A valid solution to this problem is some starting position where, starting with a tank of 0, a circular route can be made. So if we find a path from some start index to the solution index, we have a contradiction: a circuit can be made from the solution index, but since we can reach the solution index from an earlier index with a tank of at least 0 remaining, then that earlier index would be a second solution. Therefore we know end will never pass a valid solution; it will only ever reach it and stop.
    Hope this helps!

    • @ASMReading_
      @ASMReading_ Год назад +1

      My version of the code in Python. Note I didn't make a separate dif array, just updated the gas array to keep it to O(1) additional space.
      for i in range(len(gas)):
      gas[i] -= cost[i]
      start = 0
      end = 0
      tank = 0
      # initial push of end as far as possible
      while end < len(gas) and tank + gas[end] >= 0:
      tank += gas[end]
      end += 1
      # if full circle completed
      if end == len(gas):
      return 0
      # if not returned, we need more gas to proceed
      # alternate moving start and end until they meet
      start = len(gas) - 1
      tank += gas[start]
      while start != end:
      # case where more gas is needed
      if tank + gas[end] < 0:
      start -= 1
      tank += gas[start]
      # case where can proceed
      else:
      tank += gas[end]
      end += 1
      if tank >= 0:
      return start
      return -1

    • @d0m2288
      @d0m2288 10 дней назад +1

      Interesting alternative, thanks for sharing.

  • @GoodLuck-dv2zu
    @GoodLuck-dv2zu 7 месяцев назад +2

    There is something common with Kaden's algorithm. Reset the sum when the sum becomes negative.

  • @mohamedaziztousli6051
    @mohamedaziztousli6051 9 месяцев назад +1

    I submitted NeetCode's solution on (the one on Neetcode's roadmap), and it failed at test 39/40.
    class Solution:
    def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
    start, end = len(gas) - 1, 0
    total = gas[start] - cost[start]
    while start >= end:
    while total < 0 and start >= end:
    start -= 1
    total += gas[start] - cost[start]
    if start == end:
    return start
    total += gas[end] - cost[end]
    end += 1
    return -1

  • @riyanshbiswas
    @riyanshbiswas 2 года назад +1

    This is by far the best explanation to this problem. I checked a lot of other videos for this problem, but nothing got through my thick skull. You just made it simple and easy and you speak well.
    Subbed!

  • @shreyakolekar4059
    @shreyakolekar4059 9 месяцев назад +1

    There couldn't be an easier explanation to this problem other than the one you showed! Thanks @NeetCode.

  • @pingpangqiu5605
    @pingpangqiu5605 2 года назад +6

    Basically because there is only one solution, you should cumulate as much gas as you can before hitting the very first negative value . So you should start the circle right after the very last negative number

  • @pranav7471
    @pranav7471 6 месяцев назад +2

    Its not even greedy, its essentially kadane algo applied on the difference array (gas - cost array).

  • @gustavoadolfodiazcamilo2462
    @gustavoadolfodiazcamilo2462 2 года назад +2

    It seems like the opposite of greed(y) because we keep the first solution (the first starting point) we find. But it is actually greedy because we only keep it as long as it's useful for us (meaning we still have gas). So, yes that's tricky for me.

  • @jianghuilai4227
    @jianghuilai4227 3 месяца назад

    I see it in this way: Find the starting index of the maximum sum circular subarray, considering each element as gas[i] - cost[i], similar to the "Maximum Subarray Sum" problem; a valid start exists if total gas >= total cost.

  • @willshen5051
    @willshen5051 2 года назад +4

    Although sum(gas) < sum(cost) means there is no solution, it is not obvious that sum(gas) >= sum(cost) means there is a solution.
    In fact, I feel you would need math induction to prove that (I know it is valid).

    • @nikhilaradhya4088
      @nikhilaradhya4088 4 месяца назад

      I challenge you... Please prove it

    • @willshen5051
      @willshen5051 4 месяца назад

      Let me give you a brief idea, if your haven't done any math induction, you might not be able to get it.
      First, we find the diff between the gas and cost, which is also a circle, we call it D(diff).
      The problem becomes: There is a circle called as D , if sum(D) >= 0, we can always find a starting point on D, such that when doing an accumulation sum, the sum would always >= 0.
      First Step:
      If there are continuous negative numbers or positive numbers, we sum them up.
      For example,[1,2,3,-2,1,-3,-2] -> [6,-2,1,-5] we can do this because starting from 1 would be surely better than starting form 2 or 3. and we are not going to start the solution from any negative number. we call this new circle DC (compressed diff).
      Now D become [+-+-+-+-...+-], N pairs of positive and negative number. You might need to rotate the circle to make sure the first element is positive.
      Example 1 in LC:
      Input: gas = [1,2,3,4,5], cost = [3,4,5,1,2]
      Diff=[-2,-2,-2,3,3]
      CD=[-6,6] -> [6,-6] (by rotation)
      Solution: starting from CD[0], which is gas[3]
      Going to do induction on number of pairs in CD, call it N.
      Base case: K = 1, we start solution from the only positive element. It is for sure we have a solution.
      Induction: assume that when K =N -1the statement is true, show K = N is also true.
      As sum(CD) > 0, we can find at least one pair of nums [a, b], such that a +b >=0, a >0 (otherwise, if every pair has sum 0, this new circle is an example of K= N-1, and therefore it must have a solution. We call the starting point of the solution s, s would be one of the positive number in CD*.
      There are two possibilities:
      1: s is not d, we are going to show s is also solution for CD :
      we have:
      CD*: [+-,...,s-,+-,.....+-,d-,+-,......+-] where starting from s is a solution of D*.
      because s is a solution, we know sum(s,d-1)>=0 .
      as a >0, sum(s,a) >0,
      as a+b >=0, sum(s,b) >=0,
      as c >0, sum(s,c) >0.
      After c, there is no diff between D and D*, so all accumulated sum >= 0. e is a solution for CD.
      2: e is d , going to show "a" would be the solution for CD
      as a>0, a+b >=0, a+b+c >=0, we know it is not stopped at abc, then after c, there is no diff between CD and CD*. As d is solution for CD*, then "a" would be a solution for CD, (because sum(a,b,c) is d)

  • @jayb3216
    @jayb3216 Год назад

    How I see the reasoning for why the first starting index we can reach the end from is the solution:
    First, suppose we have an index i that we know we can reach the end from (without our total being < 0). We can ask ourselves this question - can the solution be before this index? Can it be past this index? The answer to both is no. It can't be before the current starting index, because any previous index would have be reset because total < 0 at some point in our algorithm. We already eliminated any index past our current index. So now the solution is either i or past i. Assume it is past i - for some j > i, j is the solution. Then we can start at j and loop back to j. But because i < j, we can go from j to i. And since we can reach the end from i, we can reach j from i. Therefore, we can start at i, go to j, then go back to i, thus making i the solution. The solution is unique so this is a contradiction. The solution cannot be past the first index i that can reach the end. It also can't be before the current index as stated earlier. It is also guaranteed to exist if we are past the initial sum check, so it must be i.

    • @starship9874
      @starship9874 8 месяцев назад

      this is very intuitive, thx!

  • @sukinkumar7042
    @sukinkumar7042 2 года назад +5

    Hey, I think you need to explain a little bit more about why you are setting the start index to i+1 when the total becomes negative. The example you took was failing on every increment but what if you pick the first number and traverse till some 50 entries and then the total becomes negative. Now you need to reset it to 51 and not to the second number. And this is precisely what makes this algorithm O(n) and not O(n^2).
    The reason why this works is that the reset happens only when the sum of the left entries to that index has become negative. And no matter wherever you start from the left, you will never reach the current index with a positive value, and that is because you have checked at each and every 'i' if it becomes negative or not.
    Hopefully, this helps someone who has a small confusion about this problem. And hey, Neetcode you are doing God's work! Appreciate it! :)

    • @ary_21
      @ary_21 2 года назад +1

      To illustrate the above point !
      Eg:
      If our difference array is
      10 10 -35 4 8
      We start from index 0
      Our total will be negative at index 2
      In that case we start from index 3 and not from index 1 , thats what the comment tries to say because any index picked between 0 and 2 as a starting point will give a negative value anyways so we begin from (2+1) th index as starting point and thats how its linear tc approach.

    • @adityavats3990
      @adityavats3990 Год назад

      This is because if sum became negative at a point you would have considered that as the breaking point already.
      When you reach a point where sum became negative you again start from the breaking point +1 as new index.

  • @The6thProgrammer
    @The6thProgrammer 8 месяцев назад

    To me the reason why this works intuitively is because once a sum goes negative we discard it. When our algorithm terminates we hope to have a positive sum for a specific segment (assuming there is a solution).
    If we know the total sum across all differences is positive, then we know all the negative segments, plus our positive segment, must be positive. Because our solution is unique we need only capture the starting point of this positive segment. And even if our solution were non-unique, by capturing the starting position of the positive sum we maximize the gas we capture.

  • @mridu299
    @mridu299 7 месяцев назад +2

    if you see the problem is asking you to find left index of maximum sum subarray so you are use kadane algorithm here

  • @nathangreene2050
    @nathangreene2050 6 месяцев назад

    Didn't see it mentioned in the comments, but I figured this one out by plotting it on a graph (gas in tank on Y axis, station index on X axis). The ideal starting position is the "absolute minimum" amount of gas in the tank (I let it go negative). The logic here is that if our tank is always "more positive" at all other gas stations relative to the starting point, we can get to the end.

  • @RobWynn
    @RobWynn 5 месяцев назад

    Simple explanation for why we can shift the result to i + 1 if we run out of gas:
    Let's imagine we start at station 0 (which we'll assume has a positive gas-cost difference), but we run out of gas at station 5. If we run out of gas at 5 even WITH station 0 (which has a positive gas value), how are we going to not run out WITHOUT it?
    Thus, we can conclude that starting at any station in between 0 and 5 (and 5 itself) will also fail to make it through station 5, and shift the starting position to i + 1.

  • @Abdulmajeed-sy1us
    @Abdulmajeed-sy1us 19 дней назад

    I could explain the reason on why it works with plain english without any equation.
    Everyone has doubt of loop
    We have two key points:
    1. There is only one answer.
    2. There will be a answer (We confirmed whether the answer exist in first if check)
    With these two, thing the negation case. We have to confirm whether current index is answer or not. Everytime we cross a index it means the index is not the answer if ever the total(surplus) goes less than zero, so if we don't get shortage while starting from an index, then we could confirm it is the solution based on above two key points.

  • @Deksudo
    @Deksudo 26 дней назад

    Proof by contradiction on why we don't need to circle back:
    First, you know at least one solution exists. Assume you started checking index i as a potential starting position, which means the solution can't be to the left (among indices 0 to i-1) because the gas dipped below zero at some point when you started from them. -> the solution should be either the current index i, or to the right (i+1 to n).
    Starting from index i, you go until the end of the array, calculating gas and cost difference, and the gas in your tank never dips below zero. Now this means that starting at index i is a valid solution to the problem, why? Because we already know there is a solution somewhere and it is somewhere along i to n. From i, you can apparently reach all of these with >= 0 gas in your tank. If you can reach the solution with a non-negative gas, that means you can go forward from that point and make a full circle. But if the solution is not i itself, that means there has to be two solutions, because you can go from i to that solution i+k and then come back to i. Since the constraints tell us that there is only one solution, that means the i must be the solution.
    Proof why we can skip the things between the starting point and the current index when the gas in the tank dips below zero:
    For an index to be a starting point, you should be able to start from that index (with a 0 - empty tank, but having extra gas won't hurt) and make a full circle. The fact that you went over them at some point means that your tank was non-negative as you crossed it, but still you ran out of gas later on. That means those indices can't be valid starting points so you can safely skip over them. This is the most important part and what makes the algorithm O(n).

  • @huhuboss8274
    @huhuboss8274 2 месяца назад

    This is easier to understand if you reframe it as finding the starting index of the maximum subarray of the differences:
    def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
    if sum(cost) > sum(gas):
    return -1
    # calculate diff
    diff = []
    sum_ = 0
    for i in range(len(gas)):
    diff.append(gas[i] - cost[i])
    sum_ += diff[i]
    # kadanes algorithm
    maxSum = float("-inf")
    curSum = 0
    index = 0
    for i, n in enumerate(diff):
    if curSum < 0:
    curSum = 0
    index = i
    curSum += n
    maxSum = max(maxSum, curSum)
    return -1 if maxSum < abs(sum_ - maxSum) else index

  • @BishalECE-uy5rn
    @BishalECE-uy5rn 2 года назад +1

    I think a little bit explanation is that as we start from an index and were able to reach the end it means all the right indexes could be the potential solution but as it is told in the problem that we have a unique solution, which implies that the indexes on the right part are just contributing to the solution but not the solution.=>Potentially could nullify the negatives which failed to reach the end of the array.

    • @Sevenk-seven
      @Sevenk-seven Год назад

      thanks

    • @Sevenk-seven
      @Sevenk-seven Год назад

      its basically like saying that since
      1> the question says there is a solution, unique
      2> the leftmost non zero candidate element is the best candidate for being the solution, as the elements to its right are adding it up further so we choose it as our solution

  • @aniketwattamwar1514
    @aniketwattamwar1514 Год назад

    I scratched my head too long for this on leetcode.
    Good explanation. Thank you for this.

  • @skyjoe3655
    @skyjoe3655 3 месяца назад

    The reason that circular check is not needed is that since the total sum is positive, the sum from the last negative point to the end is guaranteed to offset all previous negative sums and still have some surplus.

  • @fazilshafi8083
    @fazilshafi8083 5 месяцев назад

    Solution for Java folks - 💪
    class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
    int totalGas = 0;
    int totalCost = 0;
    for(int i=0; i

  • @meowmeow21588
    @meowmeow21588 Год назад

    here's my interpretation that makes the most sense: so say you have a vector of size 8, and index 5 is where you find your solution as starting point. Sure, starting at index 6 might solve the problem as well, but we are looking at a greedy solution. For example if you start at index 6 with a gas of 7 and cost of 6 , then you have one gas carrying over. But what if you start at index 5? Since we know that at index 5 (the answer) gas[5] >= cost [5], we will get some sort of extra gas carrying over to index 6, we we may get something like gas = 9 and cost = 6 there, which is more gas carrying over to further indexes. This is why the earlier index we start, given that we know the index works, the more gas we'll have down the line and the greedier we can be.

  • @sharmanihal99
    @sharmanihal99 9 месяцев назад

    Let's break down the provided solution step by step:
    Part 1: Initial check for feasibility
    The first part of the solution checks whether the total amount of gas available across all stations is sufficient to cover the total cost of the journey. If the sum of available gas is less than the sum of the costs, it implies that there isn't enough fuel to complete the circuit regardless of the starting point. In such cases, the function immediately returns -1, indicating that it's not possible to complete the journey.
    Part 2: Determining the optimal starting point
    As we iterate through the stations and update the total gas surplus/deficit, we keep track of the potential starting point (stored in the variable res).
    Consider the scenario this way: if we run out of fuel at gas station n, then to reach gas station n, all preceding stations must have either added some fuel to our tank or none at all. This implies that if we started at any of these gas stations before n, upon reaching n again, we would encounter a fuel deficit once more. Therefore, it makes more sense to start at the next gas station after n i.e., res = i+1.
    Part 3: Conclusion and optimization
    Once we've identified the optimal starting point, we return its index as the solution. Since the problem guarantees a unique solution if it exists, we don't need to continue traversing the circuit once we find the optimal starting point. This optimization ensures that the function terminates efficiently, making it run in linear time complexity (O(n)), as required by the problem constraints.
    Thanks, a lot for the solution @Neetcode.

  • @abelsimon5308
    @abelsimon5308 2 года назад +3

    as you said it, it is understandable, but not intuitive at all. Even the brute force took me more time than for a 'usual' problem. I'd classify this a brainteaser. (not sure why it has such a high frequency on leetcode tbh )

  • @mahesh_kok
    @mahesh_kok 2 года назад +1

    For People like me below solution is better readable
    def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
    if sum(gas) < sum(cost):
    return -1
    total = 0
    for index in range(len(gas)):
    total = gas[index] - cost[index]
    if total < 0:
    continue
    for inner_i in range(index+1, len(gas)):
    total = total + gas[inner_i] - cost[inner_i]
    if total < 0:
    break
    else:
    return index

  • @SiddharthJha-uj4kl
    @SiddharthJha-uj4kl Год назад

    To explain simply we are trying to find the starting index of maximum subarray of the difference array which in this case is [-2,-2,-2,3,3] so the starting index of maximum subarray is 3. if we start from 3 our total sum in this array will maximize and since there is a solution we have the maximum subarray so we can conclude that can be the only solution.

  • @crunchycho
    @crunchycho Год назад

    yo @neetcode
    another way to look at this. assuming sum(gas) = sum(cost), we know there is one unique soln.
    let diff = [gas[i] - cost[i] for i in range(len(gas))]
    sum(diff[0:res]) < 0 by construct of the algorithm
    sum(diff[res:]) >= 0 by construct
    we also know that sum(diff[:]) >=0
    Thus, sum(diff[res:]) >= abs(sum(diff[0:res])) because the sum of the parts must be >= 0

  • @Garrick645
    @Garrick645 4 месяца назад +1

    I had to return res%len(gas) becase i tool total gas

  • @bluesteel1
    @bluesteel1 10 месяцев назад

    Basically the amount of gas you carry over to the next station. eg. +3, +3, -6. Negative mean you would need 6 gas from the previous stations to proceed

  • @franco-gil
    @franco-gil Год назад

    My naive solution was O(n^2) and tooks me 3 hours to complete, now I am here in order to understand the neetcode's solutions.

  • @amitupadhyay6511
    @amitupadhyay6511 2 года назад +3

    " if you fell short, you need to gain more power "

  • @misoren9645
    @misoren9645 3 года назад +5

    Nice video.
    I know that if the sum of gas is lower than the sum of costs then there is no solution.
    But is it obvious that there must be a solution if the sum of gas is greater than the sum of cost?
    As in why can't all paths lead to a negative number before being positive, making the tank empty (or negative) before reaching end / origin?
    This can be proved. But I wanted to know if there was an immediate way to see this. As here it was kind of taken for granted.

    • @zachjcomer
      @zachjcomer 2 года назад +1

      For anyone that sees this: if we guarantee that (sum of gas) - (sum of costs) is positive, if a "path leads to a negative number" like he says, we know the path must eventually make up for that net negative fuel, as guaranteed by our net positive gas sum. So we simply start at whatever station makes up for that net negative run.
      Imagine that we have the net positive gas sum. Now imagine that for 100 stations in a row, we get 0 gas and it costs 1 gas to travel. We're at -100. But by our sum property, this deficit must be made up somewhere. Then station 101 will give us 100 gas, and we should start the cycle at station 101 (or some other net-positive run that enables us to start there).

  • @galactus889
    @galactus889 2 года назад +2

    The hard part of this problem is proving that that if the total gas is >= total cost, then there must exist a solution. That was not covered in this video. And I wasn't able to follow Leetcode's proof by contradiction so was hoping someone knew how to explain the proof in simpler terms.

    • @animeshjain4045
      @animeshjain4045 2 года назад

      This part can be best understood with an example for let us say
      Gas -> 1 1 1 1000
      Cost-> 999 1 1 1

  • @Kappalord504
    @Kappalord504 Год назад

    brute force solution that passes all the reasonable tests, except vectors that are 1X10000
    def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
    n = len(gas)
    loc_index = {}
    for i in range(n):
    if gas[i]>=cost[i]:
    loc_index[i]= 'Try this!'
    for key in loc_index:
    cur_loc = key
    # i is actual location of cur_loc in the station map, initialization
    i = cur_loc
    tank = 0
    #This checks to see if we can go through all the stations,
    while cur_loc< key +n:
    tank = tank + gas[i]
    if tank >= cost[i]:
    tank = tank - cost[i]
    cur_loc += 1
    i = cur_loc % n
    else:
    break
    if i == key %n and gas[i]>=cost[i]:
    return key
    print("point",key, ':',i)
    return -1

  • @felipemello1151
    @felipemello1151 2 года назад

    Great vid, thanks! You said in the beginning that there are not many problems like that in leetcode, but this seems to be very similar to finding the largest subarray.

  • @GodOfFaith
    @GodOfFaith Год назад

    no creater ever confessed that problem is hard ,i started your video and before even watching 10 seconds i figured out the whole solution , but yes this type of problems are more like "either you know or you don't" this was a really weird problem

  • @konradhunter1407
    @konradhunter1407 4 месяца назад

    I’d did it a slightly different way. Looped through the lists adding and subtracting to the tank. then took the index at the minimum tank. Just as fast. Needed an extra variable.

  • @starship9874
    @starship9874 8 месяцев назад

    The first index i from which we can reach the end is the solution, since the solution can't be before that (we eliminated that) and can't come after it, because any solution j that would follow our i, is reachable by i (we can reach the end so we can reach everything after i) and if j is reachable, then the supposed loop starting from j can also start from i (i => j => loop around => back to i) since there is only one solution, and in this scenario both i and j would be solution, any solution between i and the end of the list can't exist

  • @khushishah7995
    @khushishah7995 9 месяцев назад

    Very well explained, neetcode is a saviour

  • @salczar
    @salczar 8 месяцев назад +1

    I’m seeing a solution where you go backwards in the array and the index where the backwards sum is greatest is the solution?….assume the sum of cost is >= sum of gas

  • @sidazhong2019
    @sidazhong2019 10 месяцев назад

    This is hard to come up with. You can use a prefix ideal to solve the problem. It's like finding the biggest gas station in the future road.
    diff = [0] * len(gas)
    for k in range(len(gas)):
    diff[k] = gas[k] - cost[k]
    if sum(diff) < 0:
    return -1
    max_gas = (0, 0) # gas, k
    prefix_gas=0
    for k in range(len(diff)-1, -1, -1):
    prefix_gas += diff[k]
    if prefix_gas > max_gas[0]:
    max_gas = (prefix_gas ,k)
    return max_gas[1]

  • @anujapuranik2000
    @anujapuranik2000 Год назад

    Amazing explanation as always.. I totally love your videos and how simple you make them to understand them.

  • @InfoBuzz0830
    @InfoBuzz0830 2 года назад

    The way leetcode explains the problem is very complex. If the problem had a better explanation it would have helped understanding little better. But thankyou for the wonderful solution.

  • @ackeman9386
    @ackeman9386 3 месяца назад

    "I know my explanation wasn't the best". Bro I got the full picture halfway through your explanation

  • @supervince110
    @supervince110 2 года назад +2

    I find all the leetcode problems using greedy solution not related. Really hard to find a common way to solve them.

  • @PremPal-uy4nm
    @PremPal-uy4nm Год назад +1

    To be honest I don't think anybody here really understood 100% why this solution works.

  • @siddharthupadhyay4246
    @siddharthupadhyay4246 2 года назад

    Superb explanation, i was stuck with the brute force approach.

  • @hifan9091
    @hifan9091 7 месяцев назад +1

    Could somebody help explaining this:
    At 10:24, why does sum of gas >= sum of cost guarantee a solution?
    Thanks!

    • @Andrew-dd2vf
      @Andrew-dd2vf 6 месяцев назад

      it's useful to think in terms of the diff array. The condition that sum of gas >= sum of cost means that the sum of diff array >=0. This means that, there is at least one starting point from which you can iterate through the rest of the array (to be more precise, if starting point is i, you go over i, i+1, i+2 ,..., n , 0 , 1 , ... i-1, where n is the length of the array) while keeping the running sum >=0 (i.e. not running out of gas). In the example considered [-2, -2, -2, 3, 3], index 3 is the starting point, since you can go around it until index 2 and the sum of the array will always be >=0 (which means trip is complete). But of course, the condition alone does NOT tell you where the starting point is, that is done in a greedy fashion as described in the video

  • @nishantingle1438
    @nishantingle1438 2 года назад +3

    Variation of Kadane's Algorithm

  • @manne4795
    @manne4795 7 месяцев назад

    I dont know why but when I'm stuck and watch the first 2min of a neetcode explanation, I suddenly know how to solve it 😂

  • @nirajchaudhari4664
    @nirajchaudhari4664 Год назад

    I've watched almost 140 videos of yours, Would love to get an advise from you. How do you come up with such an amazing solutions to the problems? how do you approach the problems, is there any resource that I can use to improve my problem solving skills? Currently I can solve handful (very less) solve medium problems, but most of them, I'm just not able to come up with the solutions!
    I'm not sure if you're gonna reply to this comment, although trying because it will not only help me but many like me.

  • @hardeepchhabra450
    @hardeepchhabra450 Год назад

    This makes complete sense, your way of explanation makes this intuitive now, THANK YOU!

  • @mahathibodela
    @mahathibodela Год назад

    Simplyyyyy superrrr......
    Btw.. can it be done using sliding window also????

  • @purnawirmanhuang5145
    @purnawirmanhuang5145 Год назад +2

    There is a more intuitive answer in EPI (Elements of Programming Interviews) book, highly encourage to view it. The basic idea is that there is an invariant: "no matter where you start, the lowest gas tank always happen at the same index i". Thus we just need to pick i + 1 for starting point, because we know it will always go up from there.
    e.g. diff array = gas array - cost array = [-1, 0, -2, 2, 1]. The lowest point of the trip happens at index 2 (val = -2) no matter where you start the trip (try it yourself). Thus the next element (index 2 + 1 = 3) is the (unique) best starting point.

    • @davidespinosa1910
      @davidespinosa1910 8 месяцев назад

      Excellent, thank you !
      The diff array works like a conservative field.
      The partial sums work like a potential.
      We start at the minimum of the potential.
      en.wikipedia.org/wiki/Conservative_vector_field
      n = len(gas)
      s = 0
      min_val = 1000000
      min_ind = None
      for i in range(n):
      s += gas[i] - cost[i]
      if s = 0 else -1

  • @Ashadow700
    @Ashadow700 Год назад

    Thanks a lot for the explanation.
    I figured it out (eventually), but F ME, this is unintuitive.

  • @setiyakitchen
    @setiyakitchen 4 месяца назад

    class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
    int gasSum = Arrays.stream(gas).sum();
    int costSum = Arrays.stream(cost).sum();
    if(gasSum < costSum){
    return -1;
    }
    int total =0, start = 0;
    for(int i=0;i

  • @devnull711
    @devnull711 Год назад +1

    Great explanation, I implemented my own solution in a different language and got better than 100% of other submissions :)

  • @christendombaffler
    @christendombaffler Год назад

    Yeah, this problem is an interesting bait and switch. It's not regular Greedy, it's Kadane's and a less intuitive use of it at that, mostly in the sense that you really shouldn't overthink why resetting to 0 works.

  • @healing1000
    @healing1000 2 года назад

    This is a great solution and explanation thank you!

  • @НикитаБуров-ъ6р
    @НикитаБуров-ъ6р 10 месяцев назад

    just brilliant explanation

  • @nicolaurent5758
    @nicolaurent5758 3 года назад +1

    Just had an interview with this question. It took me more than 40 mins to come up with O(n^2) approach T__T

    • @王梦如-f5f
      @王梦如-f5f 3 года назад +1

      It's a tricky problem. I feel really surprised to find that there are quite a lot of companies using it to test job candidates. Did you get any comments or feedbacks from the interviewer on your answer to this question?

    • @nicolaurent5758
      @nicolaurent5758 3 года назад +2

      @@王梦如-f5f I got the feedback was positive because I could figure out and communicate well during the interview

    • @王梦如-f5f
      @王梦如-f5f 3 года назад +2

      @@nicolaurent5758 good to hear that!

    • @Coo-
      @Coo- 2 года назад

      Congrats! atleast you were able to solve it XD

    • @johnnychang3456
      @johnnychang3456 2 года назад +1

      no worries man. The brute force is actually a bit tricky as well since you have to figure out how to do a "cycle for loop". And it's almost impossible to think of this greedy method unless you already studied this kind of question before.

  • @ek3451
    @ek3451 5 месяцев назад

    A correctness proof would probably make this explanation more intuitive

  • @eamytb
    @eamytb 2 месяца назад

    Did i miss the explanation why you only iterate to the end of the array, and not to the same position? Except 100 times word “greedy”?

  • @jinyuzhang8636
    @jinyuzhang8636 Год назад

    I still don't get why we don't need to go back when we reach the last index...

  • @asdfasyakitori8514
    @asdfasyakitori8514 Год назад

    Great video

  • @po-shengwang5869
    @po-shengwang5869 Год назад

    how do you prove that as long as you can start, then there must be successful to make a circle?

  • @abhigyanraha5620
    @abhigyanraha5620 2 года назад +2

    which topic isn't tricky anymore...

  • @mashab9129
    @mashab9129 3 года назад +3

    my first solution: Runtime: 114 ms, faster than 41.03% of JavaScript online submissions for Gas Station.
    Memory Usage: 39.5 MB, less than 27.45% of JavaScript online submissions for Gas Station.
    var canCompleteCircuit = function(gas, cost) {
    const diff = Array(gas.length);
    for (let i = 0; i=0 && j=0) return i;
    }
    }
    }
    return -1;
    };

  • @kcprxkln
    @kcprxkln 2 месяца назад

    it's a pity that slower solution couldn't be accepted
    def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
    circuit_len = len(gas)
    for start in range(circuit_len):
    gas_lvl = 0
    stations_completed = 0

    while stations_completed < circuit_len:
    station = (start + stations_completed) % circuit_len
    gas_lvl += gas[station] - cost[station]
    stations_completed += 1
    if gas_lvl < 0:
    break
    if stations_completed == circuit_len:
    return start
    return -1

  • @rajivojha4887
    @rajivojha4887 Год назад

    what if the diff was [-3,-3,-3,3,2]. the algorithm would return the index of 3 even though the circular root is not possible.

    • @KishanTrivedi-ex5cg
      @KishanTrivedi-ex5cg Год назад

      Can you think of the gas and cost array as well? I feel there might not be a pair of gas and cost array where sum(gas) >= sum(cost) and also the condition of only one unique solution

  • @UrvashiCodingAcademy
    @UrvashiCodingAcademy 2 года назад

    Nice explanation bruh

  • @vrajeshbadgujar
    @vrajeshbadgujar Год назад

    Thanks

  • @citizendot1800
    @citizendot1800 2 года назад

    I just want to suggest to use enumerate rather than range(len).

    • @xxbighotshotxx
      @xxbighotshotxx 2 года назад

      Agreed. But I would first zip together the gas and cost lists first

  • @HanumantMittal
    @HanumantMittal Год назад

    How will this change if input didn’t say it is guranteed unique solution?

  • @navidr2811
    @navidr2811 3 года назад

    Thanks, great video

  • @sidd1454
    @sidd1454 Год назад

    i am sorry but i didnt understand why did i+1 didnt cause an issue at the last index

  • @dev_among_men
    @dev_among_men 3 года назад +6

    Looks like kadanes algo we pick part with maximum sum

  • @renegadezed
    @renegadezed 2 года назад

    why did you set res to 0 if you dont use it in your code?

  • @Udemys
    @Udemys 2 месяца назад

    still don't get it why don't we need to make a circle instead only till end of the array

  • @liuyanjie8365
    @liuyanjie8365 5 месяцев назад

    Thanks!

    • @NeetCode
      @NeetCode  5 месяцев назад

      Thank you so much!

  • @Mohib3
    @Mohib3 2 года назад

    is this the most optimal solution?

  • @karthik8094
    @karthik8094 Год назад

    Shouldn't start be (i+1)%n? Wheren n is len(gas)

  • @anonymousXYZ659
    @anonymousXYZ659 10 месяцев назад

    34/40 passed but time exceeded for large inputs :(

  • @SunnySpaceCat
    @SunnySpaceCat Год назад

    I don't like this problem. It's hard to understand the intuition. For me I had to think about it like this.
    Assume sum(cost) and sum(gas) are equal. Then for all i, the total gas used, [0 to i-1] + [i to n] == 0. Then 0 to i-1 has some gas deficit required to travel from 0 to i-1. This deficit must be made up for in the leftover gas from i to n because we know that no matter how you split the array in two halves the total is always zero. Thus the first i such that i-n is positive and traversable must contain the leftover gas because [0 to i-1] is exactly equal to [i to n].
    If sum(gas) is greater the logic still applies because it's still true that if there's a deficit in 0 to i-1 then i to n must be able to cover it. Thus if you can find some path from i to n that isn't broken it necessarily pays for the left half's travel (0 to i-1).

  • @spyrowolf2211
    @spyrowolf2211 2 года назад

    There really must be a better way to approach Greedy problems in general :(