041 — Combination Basic¶
এটা এই পুরো level-এর কাণ্ড (trunk) — এই একটা note পোক্ত হলে অর্ধেক level আপনিই দাঁড়িয়ে যায়। Permutation শিখেছ (order matters); এবার ঠিক উল্টোটা: শুধু দল, ক্রম অর্থহীন। 5 জন থেকে 3 জনের committee — কে আগে কে পরে তাতে কিছু আসে যায় না। এই "ক্রম ভুলে যাও" idea-টাই combination, আর DSA-র অসংখ্য counting problem-এর প্রাণ। ধীরে পড়ো, এটাই সবচেয়ে দামি।
Source¶
- Original source: Classic exercise (combination fundamentals)
- Platform: Classic exercise — https://discrete.openmathbooks.org/
- Topic: Math-based programming fundamentals → Level 3: Counting & Combinatorics
- Difficulty: Easy
- Pattern: nCr (combination / binomial coefficient)
- Basic idea inherited from: 040 — Permutation Basic (nPr কে r! দিয়ে ভাগ)
1. Problem in simple words¶
n টা আলাদা জিনিস আছে। তার মধ্যে থেকে r টা বেছে নিতে হবে — কিন্তু এবার সাজানো নয়, শুধু বাছাই। কত ভাবে করা যায়?
এই সংখ্যাটাকে বলে combination, লেখা হয় C(n, r), nCr, বা ⁿCᵣ।
সবচেয়ে জরুরি পার্থক্য: এখানে ক্রম গুরুত্বপূর্ণ নয়। মানে {A, B} দল আর {B, A} দল — একই জিনিস, একবারই গোনা হবে।
যেমন 3 জন (A, B, C) থেকে 2 জনের দল: {A,B}, {A,C}, {B,C} — মোট 3 উপায়। (permutation-এ ছিল 6, কারণ সেখানে AB আর BA আলাদা; এখানে এক।) তাই C(3, 2) = 3।
2. What is the math idea?¶
মূল idea: combination = permutation কিন্তু ভেতরের সাজানো গুনো না।
permutation-এ প্রতিটা r-জনের দলকে আবার r! ভাবে সাজানো হয়, মানে একই দল r! বার গোনা হয়ে যায়। তাই সেই overcounting ঠিক করতে r! দিয়ে ভাগ করো:
আরেকটা সুন্দর সম্পত্তি — symmetry: C(n, r) = C(n, n−r)। কারণ r জনকে বাছা মানেই বাকি n−r জনকে "বাছলাম না" বলে আলাদা করা — একই কাজ!
3. Which basic idea does this inherit from?¶
এটা সরাসরি 040 (Permutation Basic)-এর উপর দাঁড়িয়ে, যা আবার 039 (Factorial)-এর উপর। চেইনটা দেখো:
- 039:
n!= সব জিনিস সাজানো - 040:
P(n, r) = n!/(n−r)!= r জন বেছে সাজানো (order matters) - 041:
C(n, r) = P(n, r)/r!= r জন বেছে, সাজানো বাদ (order ভুলে যাও)
মূল গল্প: permutation-এ একই দলকে ভেতরে r! ভাবে সাজিয়ে r! বার গোনা হয়েছে। দল গুনতে চাই, সাজানো নয় — তাই r! দিয়ে ভাগ করে সেই বাড়তি গোনা মুছি। এই 041-ই হবে 042–048-এর প্রায় সবার ভিত্তি।
4. Real-life analogy¶
ভাবো ক্লাসে n = 5 জন আছে, আর শিক্ষক r = 3 জনের একটা group বানাবেন একসাথে project করার জন্য।
- যদি প্রতিটা group-এর ভেতরে কে leader, কে assistant — পদ থাকত, তাহলে order গুরুত্বপূর্ণ → permutation (P(5,3) = 60)।
- কিন্তু এখানে শুধু "এই 3 জন একসাথে" — কোনো পদ নেই, কে আগে নাম লিখল তাতে কিছু যায় আসে না → combination।
{রিমা, করিম, সোহা} group — তুমি যে ক্রমেই নাম ডাকো, group একটাই। তাই P(5,3) = 60-কে 3! = 6 (একই 3 জনকে সাজানোর উপায়) দিয়ে ভাগ → C(5,3) = 10 টা আলাদা group।
5. Visual explanation¶
প্রথমে দেখো permutation থেকে কীভাবে combination নামে — একই দল কতবার গোনা হয়:
3 জন (A, B, C) থেকে 2 জন:
permutation (order matters) -> 6 টা:
AB BA | AC CA | BC CB
\___/ \___/ \___/
একই দল {A,B} একই দল {A,C} একই দল {B,C}
প্রতি 2! = 2 টা সাজানো = একই দল
তাই combination = 6 / 2 = 3 টা দল: {A,B} {A,C} {B,C}
এবার দেখো formula-তে কীভাবে কাটে (C(5,3)):
P(5,3) 5 × 4 × 3 60
C(5,3) = ------ = ----------- = ----- = 10
3! 3 × 2 × 1 6
symmetry: C(5,3) = C(5,2) = 10 (3 জন বাছা = 2 জন বাদ দেওয়া)
লক্ষ করো — উপরে r টা গুণ (n থেকে নিচে), নিচে r! — দুটোতেই ঠিক r টা সংখ্যা।
6. Example input and output¶
input (n, r) -> output
------------------------
(3, 2) -> 3 (P(3,2)/2! = 6/2)
(5, 3) -> 10 (60/6)
(5, 2) -> 10 (symmetry: C(5,2) = C(5,3))
(5, 0) -> 1 (কিছু না বাছার 1 উপায়)
(5, 5) -> 1 (সবাইকে বাছার 1 উপায়)
(6, 3) -> 20
Edge case গুলো: C(n, 0) = 1 (খালি দল = 1 উপায়), C(n, n) = 1 (সবাইকে নেওয়া = 1 উপায়), আর r > n হলে 0 (যত আছে তার বেশি বাছা যায় না)।
7. Brute force thinking¶
সবচেয়ে সরাসরি — সংজ্ঞা মেনে তিনটা factorial হিসাব করে ভাগ:
def factorial(n):
result = 1
for i in range(2, n + 1):
result *= i
return result
def nCr_brute(n, r):
return factorial(n) // (factorial(r) * factorial(n - r))
এটা n! / (r! (n−r)!)-এর হুবহু অনুবাদ — ঠিক উত্তর দেয়।
verify করার জন্য আসল brute force: itertools.combinations দিয়ে ছোট case-এর সব দল generate করে গুনে formula মেলানো — combinatorics-এ এটাই সবচেয়ে নিশ্চিন্ত checker।
8. Why brute force may be slow¶
তিনটা পুরো factorial বানানো অপচয়। বড় n-এ n! মহাজাগতিক সংখ্যা, অথচ উত্তর হয়তো ছোট:
C(100, 2) = 4950 (ছোট উত্তর!)
কিন্তু brute force আগে বানায়:
100! -> 158 digit-এর দানব
2!, 98! -> আরও দুটো বিশাল সংখ্যা
তারপর ভাগ করে নামায় 4950-তে
বিশাল মাঝপথের সংখ্যা বানিয়ে তারপর কেটে ফেলা — সময় আর memory দুটোরই অপচয়। আর modular জগতে (Level 2) সরাসরি ভাগ তো নিষেধই!
9. Better thinking¶
দুটো ভালো পথ:
পথ 1 — সরাসরি গুণ-ভাগ (factorial ছাড়া): C(n, r) = (n × (n−1) × ... × (n−r+1)) / r! — মানে nPr বের করে r! দিয়ে ভাগ। মাঝপথের সংখ্যা ছোট রাখতে প্রতি ধাপে গুণ করে ভাগ:
def nCr(n, r):
if r < 0 or r > n:
return 0
r = min(r, n - r) # symmetry: কম দিকটা নাও (কম কাজ)
result = 1
for i in range(r):
result = result * (n - i) // (i + 1)
return result
প্রতি ধাপে result × (n−i) সবসময় (i+1) দিয়ে নিঃশেষে ভাগ যায় (combinatorics-এর সুন্দর সত্য), তাই integer-ই থাকে।
পথ 2 — Pascal triangle build: C(n,r) = C(n−1,r−1) + C(n−1,r) — পরের problem 042-এর মূল বিষয়।
r = min(r, n−r) লাইনটা symmetry কাজে লাগিয়ে loop ছোট করে — C(100, 98)-কে C(100, 2)-এর মতো অল্প কাজে নামায়।
10. Thinking tweak¶
আসল "আহা" মুহূর্ত: permutation গোনো, তারপর ভেতরের সাজানো (r!) ভাগ করে মুছে দাও।
মনে মনে দুই ধাপ: (১) r জন বেছে সাজাও = nPr; (২) কিন্তু আমি তো সাজানো চাই না, শুধু দল — প্রতিটা দল r! বার গোনা হয়েছে, তাই r! দিয়ে ভাগ। এই "overcount করে তারপর ভাগ করে শোধরাও" চিন্তাটা combinatorics-এর সবচেয়ে শক্তিশালী অস্ত্রগুলোর একটা — stars and bars, grid path — সবখানে ফিরবে।
আর symmetry-র tweak: C(n, r) = C(n, n−r) — r বড় হলে n−r নাও, কাজ কমে।
11. Step-by-step dry run¶
চলো C(5, 2) চালাই (path 1, r = min(2, 3) = 2, loop i = 0, 1):
| step (i) | result (শুরুতে) | result × (n−i) | // (i+1) | result (পরে) |
|---|---|---|---|---|
| শুরু | — | — | — | 1 |
| 0 | 1 | 1 × 5 = 5 | 5 // 1 | 5 |
| 1 | 5 | 5 × 4 = 20 | 20 // 2 | 10 |
Loop শেষ → result = 10 → C(5, 2) = 10 ✓।
মনে মনে মেলাও: 5 জন থেকে 2 জনের দল — {12,13,14,15,23,24,25,34,35,45} — ঠিক 10টা ✓।
12. Python solution¶
def nCr(n, r):
"""C(n, r): n জিনিস থেকে r টা বেছে নেওয়ার উপায় (order matters না)।"""
if r < 0 or r > n:
return 0
r = min(r, n - r) # symmetry: কম দিকটা নিলে loop ছোট
result = 1
for i in range(r):
result = result * (n - i) // (i + 1)
return result
# --- ছোট test গুলো (সব pass করা উচিত) ---
import math
from itertools import combinations
assert nCr(3, 2) == 3
assert nCr(5, 3) == 10
assert nCr(5, 2) == 10 # symmetry
assert nCr(5, 0) == 1
assert nCr(5, 5) == 1
assert nCr(6, 3) == 20
assert nCr(3, 5) == 0 # r > n
# math.comb দিয়ে cross-check (পুরো triangle 0..12)
for n in range(0, 13):
for r in range(0, n + 1):
assert nCr(n, r) == math.comb(n, r)
# symmetry নিজে যাচাই
for n in range(0, 13):
for r in range(0, n + 1):
assert nCr(n, r) == nCr(n, n - r)
# brute force: itertools দিয়ে সব দল গুনে formula মেলানো
def nCr_count(items, r):
return sum(1 for _ in combinations(items, r))
assert nCr(5, 2) == nCr_count([1, 2, 3, 4, 5], 2) # 10
assert nCr(6, 3) == nCr_count([1, 2, 3, 4, 5, 6], 3) # 20
print(nCr(3, 2)) # 3
print(nCr(5, 3)) # 10
print(nCr(6, 3)) # 20
print("সব test pass!")
13. Line-by-line explanation¶
পাহারা — অসম্ভব ক্ষেত্রে (negative বা r > n) উত্তর 0।
symmetry কাজে লাগাই — C(n, r) = C(n, n−r), তাই ছোট r নিয়ে loop ছোট করি।
এটাই হৃদয়। i = 0, 1, ..., r−1-এ ধাপে ধাপে উপরের গুণ (n−i) করি, সাথে সাথে নিচের (i+1) দিয়ে ভাগ। প্রতি ধাপে ভাগ নিঃশেষ হয়, তাই integer থাকে আর মাঝপথের সংখ্যা ছোট থাকে।
verify-র brute force — itertools.combinations ছোট list-এর সব r-দলের তালিকা, আমরা গুনি; formula মেলানোর নিশ্চিন্ত উপায়।
বাকি assert লাইনগুলো math.comb, symmetry আর brute count-এর সাথে মিলিয়ে চেক করছে। সব মিললে শেষে সব test pass!।
14. Output walkthrough¶
চালালে যা ছাপবে:
তিনটা print(nCr(...)) থেকে: (3,2) → 3, (5,3) → 10, (6,3) → 20। তার আগের সব assert (math.comb, symmetry, itertools brute force — তিন দিক থেকে cross-check) চুপচাপ pass করেছে। শেষে সব test pass!।
15. Time complexity¶
O(r) (আসলে O(min(r, n−r)) symmetry-র জন্য) — loop ততবার চলে, প্রতিবার একটা গুণ আর ভাগ। factorial বানানোর O(n) আর বিশাল মাঝসংখ্যা — দুটোই এড়ানো গেল।
16. Space complexity¶
O(1) — শুধু একটা result variable, কোনো table বা list নেই (উত্তর সংখ্যাটার নিজের জায়গা বাদে)।
17. Common mistakes¶
- Order matters কি না না ভেবে P বসিয়ে দেওয়া — "committee/দল/বেছে নাও" = C; "সাজাও/পদ" = P। আগে এই প্রশ্ন করো।
- তিন factorial বানিয়ে ভাগ — বড় n-এ ভয়ানক slow আর বিশাল মাঝসংখ্যা; সরাসরি গুণ-ভাগ বা Pascal নাও।
C(n, 0)বাC(n, n)ভুল ভাবা — দুটোই 1 (খালি দল, পুরো দল — প্রতিটা ঠিক একভাবে)।- Modular জগতে সরাসরি ভাগ —
% MOD-এ//কাজ করে না; তখন Level 2-এর fact + inverse pipeline (problem 034) লাগে। r > nভুলে যাওয়া — উত্তর 0; check না করলে loop ভুল করতে পারে।
18. Harder problems that inherit this idea¶
- 042 — nCr using Pascal Triangle (এই repo) — একই nCr DP-পথে।
- 045 — Count Pairs (এই repo) — C(n, 2)-এর সরাসরি প্রয়োগ।
- 048 — Count Paths in Grid (এই repo) — grid path = C(m+n, n)।
- LeetCode — Unique Paths (https://leetcode.com/problems/unique-paths/) — combination-এর জীবন্ত রূপ।
19. Pattern learned¶
Combination = permutation / r! (overcount then divide) — দল বাছতে হলে আগে সাজিয়ে গোনো (nPr), পরে r! দিয়ে ভাগ করে সাজানোর বাড়তি মুছে দাও। মূল signal: "বেছে নাও / দল / কয়টা উপসেট" দেখলেই combination — order অর্থহীন। সাথে symmetry: C(n,r) = C(n,n−r)।
20. Final summary¶
ভবিষ্যতের আমাকে এক লাইনে: "C(n,r) = n!/(r!(n−r)!) = nPr/r!; 'order matters?' না হলে combination। factorial বানিয়ো না — গুণে-ভাগে নামাও, আর C(n,r)=C(n,n−r)। এটাই level-এর কাণ্ড।"
পরের ধাপ → 042 — nCr using Pascal Triangle (একই nCr এবার DP-র চোখে — C(n,r)=C(n−1,r−1)+C(n−1,r))।
ফিরে দেখা: আগের ধাপ → 040 — Permutation Basic · এই level-এর map → ../README.md · concept ঝালাই → ../concept-notes.md · template → ../../../templates/math-problem-note-template.md।
মনে রাখার নিয়ম (legal): সব নিজের ভাষায় লেখা; problem statement copy করা হয়নি; official link শুধু attribution-এর জন্য; "asked by Google/Amazon" এমন দাবি নয় — বরং "common interview pattern" / "Google-style pattern"।