106 — Candy Distribution¶
এই level-এর একমাত্র Hard, কিন্তু ভয় পেও না — কঠিন লাগে শুধু "এক pass-এ হবে না কেন" বুঝতে। একবার দেখলে যে দুই দিকের প্রতিবেশীকে আলাদা দুই pass-এ খুশি করতে হয়, তখন সব জলের মতো পরিষ্কার। ধীরে পড়ো; two pass-এর এই trick অনেক জায়গায় ফিরবে।
Source¶
- Original source: LeetCode — Candy
- Platform: LeetCode — https://leetcode.com/problems/candy/
- Topic: Greedy math tricks → two-pass (left-to-right + right-to-left)
- Difficulty: Hard
- Pattern: two pass (এক দিক থেকে এক pass, আরেক দিক থেকে আরেক, তারপর max)
- Basic idea inherited from: — (greedy level-এর প্রথম two-pass problem)
1. Problem in simple words¶
একসারিতে n টা শিশু দাঁড়িয়ে, প্রত্যেকের একটা rating (নম্বর) আছে। তুমি ক্যান্ডি বিলি করবে এই দুই নিয়ম মেনে:
- প্রত্যেক শিশু অন্তত 1টা ক্যান্ডি পাবে
- যার rating তার পাশের প্রতিবেশীর (বাঁ বা ডান) চেয়ে বেশি, সে ওই প্রতিবেশীর চেয়ে বেশি ক্যান্ডি পাবে
প্রশ্ন: এই দুই শর্ত মেনে সবচেয়ে কম মোট কয়টা ক্যান্ডি লাগবে?
উদাহরণ: ratings = [1, 0, 2] → ক্যান্ডি [2, 1, 2] → মোট 5।
2. What is the math idea?¶
মূল কথা — প্রতিটা শিশুর শর্ত দুই দিক থেকে আসে (বাঁ প্রতিবেশী আর ডান প্রতিবেশী), আর এই দুই দিক একসাথে এক pass-এ সামলানো যায় না। তাই idea:
- প্রতিটা দিকের শর্ত আলাদা pass-এ মেটাও
- তারপর প্রতিটা শিশুর জন্য দুই pass-এর সর্বোচ্চটা (max) নাও — কারণ max নিলে কোনো দিকের শর্তই ভাঙে না, দুটোই একসাথে সত্য থাকে
এটা greedy: প্রতিটা ঘরে যতটুকু দরকার ঠিক ততটুকু ক্যান্ডি দিই, এক ফোঁটা বেশি নয়।
3. Which basic idea does this inherit from?¶
এটা greedy level-এর প্রথম two-pass problem, তাই আগের কোনো greedy-র উপর সরাসরি দাঁড়ায় না (inherited from: —)।
তবে এর শিকড় array-র উপর directional pass-এর সেই পুরোনো অভ্যাসে — বাঁ থেকে ডানে চলন্ত একটা মান ধরে রাখা (running comparison)। নতুনত্ব হলো দুই দিক থেকে দুবার চালিয়ে max দিয়ে মেলানো। এই "এক pass-এ ধরা যায় না বলে উল্টোদিক থেকে আরেক pass" idea-টা পরের অনেক DP/greedy-তে ফিরবে।
4. Real-life analogy¶
ভাবো শিক্ষক ক্যান্ডি বিলি করছেন এই নিয়মে — "যে বেশি ভালো করেছে (বেশি rating), সে তার পাশের জনের চেয়ে বেশি পাবে।"
প্রথমে শিক্ষক বাঁ দিক থেকে ডানে হাঁটেন: কোনো শিশু তার বাঁ পাশের জনের চেয়ে ভালো হলে, তাকে এক বেশি দেন। কিন্তু থামুন — ডান পাশের শর্ত তো এখনো দেখাই হয়নি! তাই শিক্ষক আবার ডান দিক থেকে বাঁয়ে হাঁটেন, এবার ডান পাশের শর্ত মেটান। আর প্রতিটা শিশুর হাতে শেষমেশ থাকে দুবারের মধ্যে যেটা বেশি — তাহলেই দুই পাশের শর্তই একসাথে মেনে চলা হয়, আর একটাও বাড়তি ক্যান্ডি নষ্ট হয় না।
5. Visual explanation¶
ratings = [1, 3, 2, 1] — দুই pass কীভাবে কাজ করে:
ratings : 1 3 2 1
শুরু : 1 1 1 1 (সবাই অন্তত 1)
pass 1 (বাঁ→ডান): rating[i] > rating[i-1] হলে candy[i]=candy[i-1]+1
i=1: 3>1 → 2
i=2: 2>3? না i=3: 1>2? না
candy : 1 2 1 1
pass 2 (ডান→বাঁ): rating[i] > rating[i+1] হলে candy[i]=max(candy[i], candy[i+1]+1)
i=2: 2>1 → max(1, 1+1)=2
i=1: 3>2 → max(2, 2+1)=3
i=0: 1>3? না
candy : 1 3 2 1 মোট = 7
কেন max? — সেই ছবি:
শিশু 1 (rating 3): বাঁ pass চাইল ≥ 2 (কারণ 3>1)
ডান pass চাইল ≥ 3 (কারণ 3>2)
max(2, 3) = 3 → দুই দাবিই একসাথে মেটে ✓
max না নিয়ে 2 দিলে ডান শর্ত ভাঙত!
6. Example input and output¶
ratings -> output (এক সম্ভাব্য বণ্টন)
-----------------------------------------------------
[1, 0, 2] -> 5 ([2, 1, 2])
[1, 2, 2] -> 4 ([1, 2, 1] — সমান rating-এ শর্ত নেই)
[1, 3, 2, 1] -> 7 ([1, 3, 2, 1])
[5] -> 1 (একটাই শিশু)
[1, 2, 3, 4, 5] -> 15 ([1, 2, 3, 4, 5], 1+2+3+4+5)
খেয়াল করো [1, 2, 2]-এ মাঝ আর শেষ শিশুর rating সমান — সমান হলে কোনো শর্ত নেই, তাই দুজনকে আলাদা সংখ্যা দেওয়ার দরকার নেই।
7. Brute force thinking¶
সরাসরি, কিন্তু ধীর চিন্তা — সবাইকে 1 দিয়ে শুরু করো, তারপর যতক্ষণ কোনো শর্ত ভাঙছে ততক্ষণ ভাঙা ঘরের ক্যান্ডি 1 করে বাড়াতে থাকো (repeated relaxation):
def candy_brute(ratings):
n = len(ratings)
candy = [1] * n
changed = True
while changed:
changed = False
for i in range(n):
if i > 0 and ratings[i] > ratings[i - 1] and candy[i] <= candy[i - 1]:
candy[i] = candy[i - 1] + 1
changed = True
if i < n - 1 and ratings[i] > ratings[i + 1] and candy[i] <= candy[i + 1]:
candy[i] = candy[i + 1] + 1
changed = True
return sum(candy)
প্রতিটা ভাঙা শর্ত ঠিক না হওয়া পর্যন্ত loop চলে, তাই শেষে সব শর্ত মেটে আর প্রতিটা মান যতটুকু দরকার ঠিক ততটুকু — অর্থাৎ minimum। ধীর, কিন্তু নির্ভুল; এটাই আমাদের যাচাইয়ের মাপকাঠি।
8. Why brute force may be slow¶
এই relaxation একবারে শুধু পাশের ঘরে তথ্য ছড়ায়, তাই একটা লম্বা বাড়তি-ক্রম (যেমন [1,2,3,...,k]) ঠিক করতে অনেকবার পুরো array ঘুরতে হয়। worst case-এ এটা প্রায় O(n²) (বা তারও বেশি pass) হয়ে যায়।
[1,2,3,...,n] হলে শেষ ঘরের তথ্য বাঁ দিক পর্যন্ত পৌঁছাতে
বহুবার pass লাগে — একই array বারবার ঘোরা।
brute force : ~O(n²) (ধীর, বহু pass)
greedy : O(n) (মাত্র দুই pass — বাঁ→ডান, ডান→বাঁ)
9. Better thinking¶
মূল observation: প্রতিটা দিকের শর্ত একটামাত্র directional pass-এ পুরোপুরি মেটানো যায়। তাই দুই pass যথেষ্ট:
def candy(ratings):
n = len(ratings)
c = [1] * n
for i in range(1, n): # pass 1: বাঁ→ডান
if ratings[i] > ratings[i - 1]:
c[i] = c[i - 1] + 1
for i in range(n - 2, -1, -1): # pass 2: ডান→বাঁ
if ratings[i] > ratings[i + 1]:
c[i] = max(c[i], c[i + 1] + 1)
return sum(c)
Greedy choice: প্রতিটা ঘরে দুই pass মিলিয়ে যতটুকু ন্যূনতম দরকার ঠিক ততটুকুই দাও (max নিয়ে, কখনো বেশি নয়)।
কেন কাজ করে (এক লাইন): pass 1-এর পর "বাঁ প্রতিবেশী" শর্ত সব ঘরে সত্য; pass 2-এ max নেওয়ায় "ডান প্রতিবেশী" শর্তও সত্য হয় আর pass 1-এর guarantee ভাঙে না (max কখনো মান কমায় না) — তাই দুই শর্ত একসাথে মেটে, এবং প্রতিটা মান ন্যূনতম রাখায় মোটও minimum।
10. Thinking tweak¶
এক লাইনের "আহা!":
"বাঁ আর ডান প্রতিবেশীর শর্ত একসাথে এক pass-এ ধরা যায় না — তাই দুই দিক থেকে দুবার চালাও, আর
maxদিয়ে মেলাও যাতে কোনো দিকের জয় নষ্ট না হয়।"
এক pass-এ চেষ্টা করলে ডান প্রতিবেশীর তথ্য বাঁ থেকে যেতে যেতে জানাই যায় না — এই উপলব্ধিটাই tweak। আর max-এর জাদু: একদিকের অর্জন রক্ষা করেই অন্যদিকের শর্ত জোড়ে।
11. Step-by-step dry run¶
ratings = [1, 3, 2, 1] ধীরে চালাই।
শুরু: c = [1, 1, 1, 1]
Pass 1 (বাঁ→ডান):
| i | ratings[i] > ratings[i-1]? | c[i] |
|---|---|---|
| 1 | 3 > 1 হ্যাঁ | c[0]+1 = 2 |
| 2 | 2 > 3 না | 1 |
| 3 | 1 > 2 না | 1 |
pass 1 শেষে: c = [1, 2, 1, 1]
Pass 2 (ডান→বাঁ):
| i | ratings[i] > ratings[i+1]? | c[i] = max(c[i], c[i+1]+1) |
|---|---|---|
| 2 | 2 > 1 হ্যাঁ | max(1, 1+1) = 2 |
| 1 | 3 > 2 হ্যাঁ | max(2, 2+1) = 3 |
| 0 | 1 > 3 না | 1 |
pass 2 শেষে: c = [1, 3, 2, 1] → মোট = 7। প্রতিটা ঘরে এখন দুই দিকের শর্তই সত্য।
12. Python solution¶
def candy(ratings):
"""two-pass greedy — minimum মোট ক্যান্ডি।"""
n = len(ratings)
c = [1] * n
for i in range(1, n): # pass 1: বাঁ→ডান
if ratings[i] > ratings[i - 1]:
c[i] = c[i - 1] + 1
for i in range(n - 2, -1, -1): # pass 2: ডান→বাঁ
if ratings[i] > ratings[i + 1]:
c[i] = max(c[i], c[i + 1] + 1)
return sum(c)
def candy_brute(ratings):
"""relaxation দিয়ে minimum — ধীর কিন্তু নির্ভুল (যাচাইয়ের জন্য)।"""
n = len(ratings)
c = [1] * n
changed = True
while changed:
changed = False
for i in range(n):
if i > 0 and ratings[i] > ratings[i - 1] and c[i] <= c[i - 1]:
c[i] = c[i - 1] + 1
changed = True
if i < n - 1 and ratings[i] > ratings[i + 1] and c[i] <= c[i + 1]:
c[i] = c[i + 1] + 1
changed = True
return sum(c)
# --- ছোট test (সব pass করা উচিত) ---
assert candy([1, 0, 2]) == 5
assert candy([1, 2, 2]) == 4
assert candy([1, 3, 2, 1]) == 7
assert candy([5]) == 1
assert candy([1, 2, 3, 4, 5]) == 15
assert candy([5, 4, 3, 2, 1]) == 15
# two-pass আর brute force ছোট সব rating-এ একমত (brute = সত্য)
import itertools
for n in range(1, 7):
for r in itertools.product(range(0, 3), repeat=n):
assert candy(list(r)) == candy_brute(list(r))
print(candy([1, 0, 2])) # 5
print(candy([1, 3, 2, 1])) # 7
print(candy([1, 2, 3, 4, 5])) # 15
print("সব test pass!")
13. Line-by-line explanation¶
প্রথম শর্ত — সবাই অন্তত 1টা ক্যান্ডি পায়।
pass 1, বাঁ→ডান। কোনো শিশু তার বাঁ প্রতিবেশীর চেয়ে ভালো হলে, তাকে বাঁ-জনের চেয়ে 1 বেশি দিই। এই pass শেষে সব ঘরে বাঁ প্রতিবেশীর শর্ত সত্য।
pass 2, ডান→বাঁ। ডান প্রতিবেশীর শর্ত মেটাই, কিন্তু max দিয়ে — যাতে pass 1-এ পাওয়া বাঁ-দিকের guarantee ভেঙে না যায়। max কখনো মান কমায় না, শুধু দরকারে বাড়ায়।
প্রতিটা শিশুর ন্যূনতম দরকার মেটানো ক্যান্ডির যোগফলই minimum মোট।
candy_brute repeated relaxation দিয়ে একই minimum বের করে (ধীর); itertools.product দিয়ে ছোট সব rating-array বানিয়ে two-pass আর brute মিলিয়ে দেখা হয়েছে।
14. Output walkthrough¶
চালালে ছাপবে:
প্রথম তিন line print থেকে: [1,0,2] → 5, [1,3,2,1] → 7, [1,2,3,4,5] → 15। তার আগে সব assert (সরাসরি case + itertools দিয়ে ছোট সব rating-array) চুপচাপ pass করেছে, তাই শেষে সব test pass!।
15. Time complexity¶
O(n) — ঠিক দুটো pass, প্রতিটা array-র উপর একবার; প্রতিটা ঘরে constant কাজ। brute force-এর ~O(n²) থেকে বড় উন্নতি।
16. Space complexity¶
O(n) — একটা c array লাগে n শিশুর ক্যান্ডি ধরে রাখতে। (চাইলে দুই আলাদা array দিয়েও করা যায়, কিন্তু এক array + max-ই যথেষ্ট।)
17. Common mistakes¶
- এক pass-এ সমাধানের চেষ্টা — সবচেয়ে বড় ভুল; ডান প্রতিবেশীর তথ্য বাঁ→ডান pass-এ জানা যায় না, দুই pass লাগবেই।
- Pass 2-এ
maxনা নেওয়া — সরাসরিc[i] = c[i+1] + 1লিখলে pass 1-এর বাঁ-দিকের guarantee ভেঙে যেতে পারে; সবসময়max(c[i], c[i+1]+1)। - সমান rating-এ শর্ত বসানো — শর্ত শুধু কড়া বড় (
>)-র জন্য; সমান rating-এ কোনো দাবি নেই, তাই অহেতুক বাড়িও না। - সবাইকে 1 দিয়ে শুরু না করা — base হিসেবে সবার 1 দরকার (ন্যূনতম শর্ত); 0 দিলে ভুল।
- শুধু একদিকের pass করা — শুধু বাঁ→ডান বা শুধু ডান→বাঁ করলে অন্য পাশের শর্ত ভাঙবে; দুটোই চাই।
18. Harder problems that inherit this idea¶
- LeetCode — Trapping Rain Water (https://leetcode.com/problems/trapping-rain-water/) — ক্লাসিক two-pass (left-max ও right-max আলাদা pass, তারপর min) — একই "দুই দিক থেকে তথ্য জড়ো" দর্শন।
- LeetCode — Product of Array Except Self (https://leetcode.com/problems/product-of-array-except-self/) — prefix product (বাঁ→ডান) ও suffix product (ডান→বাঁ) — two-pass-এর আরেক রূপ।
- LeetCode — Candy (https://leetcode.com/problems/candy/) — মূল problem নিজেই; constant-space O(1) variant ভাবলে আরও গভীরে যাওয়া যায় (peak/valley গোনা)।
19. Pattern learned¶
Two pass (directional sweeps + max/min merge) — যখন এক ঘরের শর্ত দুই দিক থেকে আসে, প্রতিটা দিক আলাদা pass-এ মেটাও, তারপর max (বা min) দিয়ে মেলাও যাতে কোনো দিকের guarantee না ভাঙে। বড় শিক্ষা: এক pass-এ দুই দিকের তথ্য ধরা না গেলে, উল্টোদিক থেকে আরেক pass চালাও।
20. Final summary¶
ভবিষ্যতের আমাকে এক লাইনে: "Candy = two pass; বাঁ→ডান-এ বাঁ শর্ত, ডান→বাঁ-এ max দিয়ে ডান শর্ত — দুই দিকের দাবি একসাথে মেটাও, প্রতিটা ঘরে ন্যূনতম দিয়ে।"
পরের ধাপ → 107 — Interval Scheduling (exchange argument-এর সবচেয়ে পরিষ্কার উদাহরণ — earliest finish first)।
ফিরে দেখা: এই 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"।