Skip to content

105 — Gas Station

104-এ শিখলে "এক pass, এক সংখ্যা track" — এবার সেই একই দর্শন বৃত্তাকার রাস্তায়! Gas Station দেখতে জটিল মনে হয় (বৃত্ত, জ্বালানি, খরচ...), কিন্তু দুটো ছোট observation ধরলেই পুরো O(n²) brute force গলে এক pass-এ নেমে আসে। ধীরে পড়ো — "total ≥ 0 হলেই answer আছে" আর "reset point" — এই দুই রত্ন এখানকার আসল শিক্ষা।

Source

  • Original source: LeetCode — Gas Station
  • Platform: LeetCode — https://leetcode.com/problems/gas-station/
  • Topic: Greedy math tricks → reset point, prefix-sum observation
  • Difficulty: Medium
  • Pattern: reset point (tank negative হলে নতুন start)
  • Basic idea inherited from: 104 — Jump Game (এক pass-এ একটা সংখ্যা track)

1. Problem in simple words

বৃত্তাকার রাস্তায় n টা gas station সাজানো। station i-তে gas[i] পরিমাণ জ্বালানি পাবে, আর সেখান থেকে পরের station-এ যেতে cost[i] জ্বালানি খরচ হবে। তোমার গাড়ির ট্যাঙ্ক শুরুতে খালি, ধারণক্ষমতা অসীম।

তুমি কোনো এক station থেকে শুরু করে পুরো বৃত্ত ঘুরে আবার সেখানে ফিরতে চাও। প্রশ্ন: কোন station থেকে শুরু করলে এটা সম্ভব? সম্ভব হলে সেই শুরুর index, নাহলে -1।

(নিশ্চয়তা: উত্তর থাকলে সেটা একটাই — unique।)

2. What is the math idea?

দুটো জিনিস। ধরো প্রতিটা station-এ লাভ-ক্ষতি gain[i] = gas[i] - cost[i]

  • Total observation: পুরো বৃত্তের sum(gain) < 0 হলে কোনো start-ই কাজ করবে না (মোট জ্বালানিই খরচের চেয়ে কম); আর sum(gain) ≥ 0 হলে অন্তত একটা valid start আছেই
  • Reset observation: station s থেকে শুরু করে চলতে চলতে station i-তে tank প্রথমবার negative হলে — s থেকে i পর্যন্ত মাঝের কোনো station-ই valid start নয়। তাই সোজা i + 1 থেকে নতুন করে শুরু করো।

এই দুই observation মিলিয়ে এক pass-এই উত্তর।

3. Which basic idea does this inherit from?

সরাসরি 104 (Jump Game)-এর কাঁধে দাঁড়ানো। দুটোতেই দর্শন এক: array-র উপর এক pass, হাতে গুটিকয় সংখ্যা (এখানে total, tank, start), আর একটা চালাক observation যা সব সম্ভাবনা ঘাঁটা থেকে বাঁচায়।

104-এ track করতাম "সর্বোচ্চ কোথায় পৌঁছানো সম্ভব"; এখানে track করছি "চলন্ত ট্যাঙ্ক কখন negative হলো"। আর tank আসলে একটা prefix sum (চলন্ত যোগফল) — সেই prefix-sum-এর চিন্তা level 5 থেকেই চেনা, এখানে greedy reset-এর সাথে জুড়ে গেছে।

4. Real-life analogy

ভাবো তুমি বৃত্তাকার একটা রাস্তায় বাইক চালিয়ে পুরো চক্কর দিতে চাও, পথে পথে পেট্রল পাম্প। কোনো পাম্প থেকে শুরু করে চলতে চলতে যদি পেট্রল ফুরিয়ে যায় (tank negative), তাহলে শুধু সেই শুরুর পাম্পই বাদ যায় না — মাঝের সব পাম্পও বাদ। কারণ মাঝের কোনো পাম্প থেকে শুরু করলে এ পর্যন্ত পথে জমানো বাড়তি পেট্রলটাই তো তুমি পেতে না, তাই আরও আগেই আটকে যেতে।

তাই বুদ্ধিমানের কাজ — যেখানে আটকালে, তার ঠিক পরের পাম্প থেকে নতুন করে চেষ্টা করা। আর মোট হিসাবে যদি পুরো রাস্তায় পাওয়া পেট্রল ≥ মোট খরচ হয়, তবে কোথাও না কোথাও থেকে শুরু করলে চক্কর সম্পূর্ণ হবেই।

5. Visual explanation

gas = [1, 2, 3, 4, 5], cost = [3, 4, 5, 1, 2]gain = [-2, -2, -2, 3, 3]:

station :    0    1    2    3    4
gain    :   -2   -2   -2    3    3      sum = 0  ≥ 0 → answer আছে

start=0: tank: -2 → negative! reset → start=1, tank=0
start=1: tank: -2 → negative! reset → start=2, tank=0
start=2: tank: -2 → negative! reset → start=3, tank=0
start=3: tank: 3 → 6 (ঘুরে শেষ পর্যন্ত আর negative হলো না)

total = 0 ≥ 0  ⇒  answer = start = 3  ✓

reset কীভাবে মাঝের সবাইকে একসাথে বাদ দেয়, সেই ছবি:

start=s ───X (i-তে tank negative)
   │       │
   └─ s, s+1, ..., i  সবাই বাদ  ─┘   পরের চেষ্টা: i+1 থেকে

কারণ: s থেকে i-এর মাঝের যেকোনো station থেকে শুরু করলে
      পথের জমানো বাড়তি fuel হারাতে → i-তে tank আরও কম থাকত।

6. Example input and output

gas              cost             ->  output  (ব্যাখ্যা)
-----------------------------------------------------------------
[1,2,3,4,5]      [3,4,5,1,2]      ->  3       (station 3 থেকে চক্কর সম্ভব)
[2,3,4]          [3,4,3]          ->  -1      (sum gain = -1 < 0, অসম্ভব)
[5]              [4]              ->  0       (একটাই station, gain +1)
[3,1,1]          [1,2,2]          ->  0       (start 0 থেকেই চলে)
[2]              [2]              ->  0       (gain 0, একটাই station — চক্করই শেষ)

মনে রাখো: sum(gas) < sum(cost) মানেই -1 (মোট জ্বালানি কম); আর answer থাকলে সেটা সবসময় unique।

7. Brute force thinking

সরাসরি চিন্তা — প্রতিটা station কে শুরু ধরে পুরো বৃত্ত simulate করে দেখো। কোনো একটা শুরু থেকে n টা step ঘুরে আসা গেলে সেটাই উত্তর:

def gas_station_brute(gas, cost):
    n = len(gas)
    for start in range(n):
        tank = 0
        ok = True
        for step in range(n):
            i = (start + step) % n
            tank += gas[i] - cost[i]
            if tank < 0:
                ok = False
                break
        if ok:
            return start
    return -1

এটা সব শুরু চেষ্টা করে দেখে, তাই সবসময় সঠিক — আমাদের যাচাইয়ের মাপকাঠি।

8. Why brute force may be slow

বাইরের loop n টা start, ভেতরের loop প্রতিটার জন্য n টা step — মোট O(n²)। n যদি 10⁵ হয়, তাহলে প্রায় 10¹⁰ operation — interview-তে Time Limit Exceeded।

প্রতিটা start থেকে আবার শুরু করে পুরো বৃত্ত ঘোরা মানে
একই কাজ বারবার — আগের simulate-এর তথ্য ফেলে দিচ্ছি।

brute force : O(n²)  (ধীর)
greedy      : O(n)   (এক pass — reset observation কাজ অর্ধেক করে দেয়)

9. Better thinking

দুই observation কাজে লাগাই। এক pass-এ total (পুরো বৃত্তের gain যোগফল) আর tank (বর্তমান চলন্ত যোগফল) রাখি; tank negative হলেই start reset করে পরের station-এ পাঠাই:

def gas_station(gas, cost):
    total, tank, start = 0, 0, 0
    for i in range(len(gas)):
        gain = gas[i] - cost[i]
        total += gain
        tank += gain
        if tank < 0:
            start = i + 1     # reset: মাঝের কাউকে আর try করতে হবে না
            tank = 0
    return start if total >= 0 else -1

Greedy choice: যেখানে tank প্রথমবার negative হয়, তার ঠিক পরের station থেকে নতুন start ধরো (মাঝের সবাইকে একসাথে বাদ দাও)।

কেন কাজ করে (exchange argument, এক লাইন): ধরো optimal start কোনো station s থেকে i-এর মাঝে; কিন্তু start..i চলার সময় tank negative হয়েছে, মানে start থেকে যেকোনো মাঝের station পর্যন্ত prefix sum ≤ 0 — তাই সেই মাঝের station থেকে শুরু করলে i-তে tank আরও ছোট হতো, কখনো বড় নয়; সুতরাং মাঝের কাউকে i+1 দিয়ে বদলালে কিছু হারাই না, বরং unique optimal-টা i+1-এর পরেই থাকে।

10. Thinking tweak

দুটো এক-লাইনের "আহা!":

  1. "চক্কর সম্ভব কিনা — পুরো বৃত্তের total gain-ই বলে দেয়; ≥ 0 হলেই answer আছে।"

  2. "কোথাও আটকালে শুধু শুরুর station নয়, মাঝের সবাই বাদ — এক লাফে i+1-এ চলে যাও।"

এই দ্বিতীয় tweak-টাই (reset = মাঝের সবাই বাদ) O(n²)-কে O(n)-এ নামায়। প্রথমবার আটকে গিয়ে "আবার একটা একটা করে চেষ্টা করি" না ভেবে "একসাথে সব বাদ" ভাবাটাই আসল মোচড়।

11. Step-by-step dry run

gas = [1, 2, 3, 4, 5], cost = [3, 4, 5, 1, 2] ধীরে চালাই:

i gain = gas[i]-cost[i] total tank (যোগের পর) tank < 0? start
0 1-3 = -2 -2 -2 হ্যাঁ → reset 1 (tank→0)
1 2-4 = -2 -4 -2 হ্যাঁ → reset 2 (tank→0)
2 3-5 = -2 -6 -2 হ্যাঁ → reset 3 (tank→0)
3 4-1 = 3 -3 3 না 3
4 5-2 = 3 0 6 না 3

loop শেষে total = 0 ≥ 0, তাই answer = start = 3। লক্ষ করো — start বারবার reset হয়ে 3-এ থিতু হলো, আর total ঠিক হিসাব রাখল উত্তর আদৌ আছে কিনা।

12. Python solution

def gas_station(gas, cost):
    """reset point greedy — এক pass-এ valid start (বা -1)।"""
    total, tank, start = 0, 0, 0
    for i in range(len(gas)):
        gain = gas[i] - cost[i]
        total += gain
        tank += gain
        if tank < 0:
            start = i + 1
            tank = 0
    return start if total >= 0 else -1


def gas_station_brute(gas, cost):
    """প্রতিটা start simulate — O(n²), নির্ভুল (যাচাইয়ের জন্য)।"""
    n = len(gas)
    for start in range(n):
        tank = 0
        ok = True
        for step in range(n):
            i = (start + step) % n
            tank += gas[i] - cost[i]
            if tank < 0:
                ok = False
                break
        if ok:
            return start
    return -1


# --- ছোট test (সব pass করা উচিত) ---
assert gas_station([1, 2, 3, 4, 5], [3, 4, 5, 1, 2]) == 3
assert gas_station([2, 3, 4], [3, 4, 3]) == -1
assert gas_station([5], [4]) == 0
assert gas_station([2], [2]) == 0
assert gas_station([3, 1, 1], [1, 2, 2]) == 0

# greedy আর brute force ছোট সব case-এ একমত (brute = সত্য)
import itertools
for n in range(1, 5):
    for gas in itertools.product(range(0, 4), repeat=n):
        for cost in itertools.product(range(0, 4), repeat=n):
            assert gas_station(list(gas), list(cost)) == gas_station_brute(list(gas), list(cost))

print(gas_station([1, 2, 3, 4, 5], [3, 4, 5, 1, 2]))   # 3
print(gas_station([2, 3, 4], [3, 4, 3]))               # -1
print(gas_station([3, 1, 1], [1, 2, 2]))               # 0
print("সব test pass!")

13. Line-by-line explanation

total, tank, start = 0, 0, 0

total = পুরো বৃত্তের gain যোগফল (answer আদৌ আছে কিনা বলবে); tank = বর্তমান চলন্ত fuel; start = এখন পর্যন্ত আমাদের সেরা candidate শুরু।

gain = gas[i] - cost[i]
total += gain
tank  += gain

প্রতিটা station-এ লাভ-ক্ষতি বের করে দুই হিসাবেই যোগ করি — একটা মোট (total), একটা চলন্ত (tank)।

if tank < 0:
    start = i + 1
    tank = 0

এটাই reset-এর হৃদয়। tank negative হলে start..i সবাই বাতিল, তাই start কে i+1-এ ঠেলে দিই আর tank শূন্য করে নতুন চক্কর শুরু করি।

return start if total >= 0 else -1

total ≥ 0 হলে আমাদের শেষ candidate start-ই উত্তর (observation 1 নিশ্চয়তা দেয় এটা valid); নাহলে -1।

gas_station_brute প্রতিটা start থেকে পুরো বৃত্ত simulate করে — ধীর কিন্তু নির্ভুল। itertools.product দিয়ে ছোট সব (gas, cost) জোড়া বানিয়ে greedy আর brute মিলিয়ে দেখা হয়েছে।

14. Output walkthrough

চালালে ছাপবে:

3
-1
0
সব test pass!

প্রথম তিন line print থেকে: প্রথম case-এ station 3 থেকে চক্কর সম্ভব; দ্বিতীয়টায় মোট জ্বালানি কম তাই -1; তৃতীয়টায় station 0 থেকেই চলে। তার আগে সব assert (সরাসরি case + itertools দিয়ে শত শত ছোট জোড়া) চুপচাপ pass করেছে, তাই শেষে সব test pass!

15. Time complexity

O(n) — array-র উপর ঠিক একবার pass; প্রতিটা station-এ constant কাজ। reset observation মাঝের সব station একসাথে বাদ দেয় বলে আর দ্বিতীয়বার ঘোরা লাগে না। brute force-এর O(n²) থেকে বিরাট উন্নতি।

16. Space complexity

O(1) — শুধু তিনটা সংখ্যা (total, tank, start); কোনো বাড়তি array নয়।

17. Common mistakes

  • O(n²) brute force-এই থেমে যাওয়া — প্রতিটা start simulate করলে slow; reset observation না ধরলে এক pass-এ নামানো যায় না।
  • Reset-এ শুধু শুরুর station বাদ দেওয়া — tank negative হলে মাঝের সব station বাদ; start = i + 1 করতে হয়, start += 1 নয়।
  • total আর tank গুলিয়ে ফেলাtotal শেষে answer আছে কিনা ঠিক করে; tank reset হয় বলে সে এই কাজ করতে পারে না। দুটো আলাদা রাখো।
  • tank reset করতে ভুলে যাওয়াstart পাল্টে tank = 0 না করলে পরের চক্করের হিসাব ভুল হবে।
  • Unique answer ধরে না নেওয়া — answer থাকলে একটাই; তাই প্রথম valid start-ই চূড়ান্ত, আর খুঁজতে হয় না।

18. Harder problems that inherit this idea

  • LeetCode — Maximum Subarray (Kadane) (https://leetcode.com/problems/maximum-subarray/) — একই "চলন্ত যোগফল negative হলে reset" দর্শন (running sum < 0 → 0 থেকে শুরু)।
  • LeetCode — Jump Game (https://leetcode.com/problems/jump-game/) — এই repo-রই 104, যেখান থেকে "এক pass, এক সংখ্যা" দর্শন এসেছে।
  • CSES / Codeforces circular-array problem গুলো — বৃত্তাকার array-তে prefix-sum + reset-এর এই কৌশল বারবার ফেরে।

19. Pattern learned

Reset point (prefix-sum + restart) — চলন্ত যোগফল রাখো; নির্দিষ্ট শর্তে (এখানে tank < 0) candidate reset করে পরের index-এ লাফাও, আর আলাদা একটা মোট হিসাব দিয়ে feasibility ঠিক করো। বড় শিক্ষা: "আটকে গেলে এক লাফে মাঝের সবাইকে বাদ দাও" — এই reset-ই O(n²)-কে O(n)-এ নামায়।

20. Final summary

ভবিষ্যতের আমাকে এক লাইনে: "Gas Station = reset point; total gain ≥ 0 হলে answer আছে, আর tank < 0 হলেই start = i+1, tank = 0 (মাঝের সবাই বাদ)। দুই observation = এক pass O(n)।"

পরের ধাপ → 106 — Candy Distribution (এবার দুই দিকের শর্ত একসাথে সামলানো — two pass greedy)।

ফিরে দেখা: এই level-এর map → ../README.md · concept ঝালাই → ../concept-notes.md · note-টা যে ছকে লেখা → ../../../templates/math-problem-note-template.md

মনে রাখার নিয়ম (legal): সব নিজের ভাষায় লেখা; কোনো problem statement copy করা হয়নি; official link শুধু attribution-এর জন্য; "asked by Google/Amazon" এমন দাবি নয় — বরং "common interview pattern" / "Google-style pattern"।