013 — Count of Smaller Numbers After Self¶
Source¶
- Original source: LeetCode 315 — Count of Smaller Numbers After Self
- Platform: LeetCode 315
- Topic: segment tree and fenwick tree
- Difficulty: Hard
- Pattern: BIT + coordinate compression
- Basic idea inherited from: BIT over values, sorting
1. Problem in simple words¶
একটা array a দেওয়া। প্রতিটা index i-র জন্য বের করো: i-র ডানদিকে কতগুলো element আছে যেগুলো a[i]-এর চেয়ে ছোট। উত্তর একটা array counts, যেখানে counts[i] = ওই সংখ্যা।
a = [5, 2, 6, 1]
counts = [2, 1, 1, 0]
a[0]=5 -> ডানে ছোট: {2, 1} -> 2
a[1]=2 -> ডানে ছোট: {1} -> 1
a[2]=6 -> ডানে ছোট: {1} -> 1
a[3]=1 -> ডানে ছোট: {} -> 0
2. Which basic idea does this inherit from?¶
এটা BIT-over-values আর sorting-এর উপর দাঁড়িয়ে। #10-এ তুমি মোট inversion গুনেছিলে; এটা ঠিক তার per-element রূপ — প্রতিটা element আলাদা করে জানতে চায় তার ডানে কয়টা ছোট। BIT এখানে আবার একটা frequency table over values।
3. What is the hidden math or logic?¶
লুকানো logic: array ডান থেকে বাঁয়ে process করো; প্রতিটা value frequency table-এ tick দেওয়ার আগে জিজ্ঞেস করো "এ পর্যন্ত (অর্থাৎ আমার ডানে) কয়টা value আমার চেয়ে ছোট দেখা হয়েছে"। সেই answer-টাই counts[i]। ডান-থেকে-বাঁ order নিশ্চিত করে BIT-এ শুধু "ডানের" element-রাই আছে।
4. Which data structure is needed?¶
একটা Fenwick tree (BIT) value-দের rank-এ indexed। Value 10^4 পর্যন্ত বড় (আর negative) হতে পারে, তাই আগে coordinate compression (value → rank)।
5. Why this data structure fits¶
"আমার চেয়ে ছোট কয়টা ইতিমধ্যে দেখেছি" = prefix_sum(rank - 1) over a frequency table। BIT এই prefix-count আর point-increment দুটোই O(log n)-এ দেয়। n element-এ মোট O(n log n) — segment tree-ও পারত, কিন্তু BIT ছোট আর দ্রুত (../fenwick-tree.md section 8-9 দেখো)।
6. Real-life analogy¶
ভাবো একটা দৌড় শেষে finishers এক এক করে ভেতরে ঢুকছে, সবার গায়ে bib-number। শেষ থেকে দেখলে — প্রতিটা নতুন finisher-এর জন্য তুমি জানতে চাও "এর পরে যারা এসেছে (মানে board-এ ইতিমধ্যে আছে), তাদের কয়জনের bib এর চেয়ে ছোট"। প্রতিবার পুরো board না গুনে, bib-number অনুযায়ী একটা tally রাখো; নতুন bib-এ শুধু তার নিচের অংশটা যোগ করো।
7. Visual explanation¶
a = [5, 2, 6, 1] compressed rank (1->1, 2->2, 5->3, 6->4):
a: 5 2 6 1
rank: 3 2 4 1
ডান -> বাঁ, frequency BIT-এ tick দিতে দিতে:
a[3]=1 (rank1): prefix_sum(0) = 0 -> counts[3]=0; add(1,1)
a[2]=6 (rank4): prefix_sum(3) = 1 -> counts[2]=1; add(4,1) (শুধু 1 marked)
a[1]=2 (rank2): prefix_sum(1) = 1 -> counts[1]=1; add(2,1) (শুধু 1)
a[0]=5 (rank3): prefix_sum(2) = 2 -> counts[0]=2; add(3,1) (1, 2)
counts = [2, 1, 1, 0]
8. Example input and output¶
input: a = [5, 2, 6, 1]
output: [2, 1, 1, 0]
input: a = [-1]
output: [0] (একা, ডানে কেউ নেই)
input: a = [-1, -1]
output: [0, 0] (equal, strictly ছোট নয়)
9. Brute force thinking¶
সরল চিন্তা: প্রতি i-র জন্য ডানদিকের সব element ঘুরে ছোট গোনা।
নির্ভুল, এটাই reference।
10. Why brute force is slow¶
দুই nested loop = O(n^2)। n = 10^5 হলে 10^10 — TLE। BIT-over-values প্রতি element-এ একটা prefix-count + একটা update দিয়ে পুরোটা O(n log n)-এ নামায়।
11. Key observation¶
মূল observation: "ডানে কয়টা ছোট" = ডান-থেকে-বাঁ গেলে "এ পর্যন্ত দেখা value-গুলোর মধ্যে কয়টা ছোট"। Order উল্টে দিলে ভবিষ্যৎ (ডান) অতীত হয়ে যায়, আর প্রশ্নটা একটা frequency-table prefix-count-এ পরিণত হয়।
12. Optimized intuition¶
Coordinate compression করে value → rank। তারপর i = n-1 থেকে 0 পর্যন্ত: counts[i] = prefix_sum(rank[i] - 1) (strictly ছোট), তারপর add(rank[i], 1)। rank - 1 strict ছোট নিশ্চিত করে (equal value বাদ)। ডান-থেকে-বাঁ order মানে BIT-এ শুধু ডানের element।
13. Thinking tweak¶
মোচড়: per-element "ডানে কয়টা ছোট" প্রশ্নকে একটা running frequency table-এর উপর prefix-count-এ অনুবাদ করো, আর array উল্টো দিকে হাঁটো। এই reverse-sweep + BIT-frequency জোড়া #14, #16-এও ফিরে আসবে।
14. Step-by-step dry run¶
a = [5, 2, 6, 1], rank {1:1, 2:2, 5:3, 6:4}, BIT খালি। i = 3 → 0:
- i=3, a=1 (r=1):
prefix_sum(0)=0→ counts[3]=0;add(1,1)। - i=2, a=6 (r=4):
prefix_sum(3)=1(শুধু r1) → counts[2]=1;add(4,1)। - i=1, a=2 (r=2):
prefix_sum(1)=1(শুধু r1) → counts[1]=1;add(2,1)। - i=0, a=5 (r=3):
prefix_sum(2)=2(r1, r2) → counts[0]=2;add(3,1)। - counts =
[2, 1, 1, 0]। ✓
15. Python solution¶
class Fenwick:
"""1-indexed BIT, value-দের frequency table হিসেবে। index মানে VALUE-র rank,
position নয়। add(i, 1): value-rank i একবার দেখলাম; prefix_sum(i): rank 1..i-র
মোট গোনা। দুটোই O(log n)।"""
def __init__(self, n):
self.n = n
self.tree = [0] * (n + 1)
def add(self, i, delta):
while i <= self.n:
self.tree[i] += delta
i += i & (-i)
def prefix_sum(self, i):
s = 0
while i > 0:
s += self.tree[i]
i -= i & (-i)
return s
def count_smaller(a):
n = len(a)
if n == 0:
return []
# coordinate compression: value -> 1-indexed rank
order = sorted(set(a))
rank = {v: idx + 1 for idx, v in enumerate(order)}
bit = Fenwick(len(order))
counts = [0] * n
for i in range(n - 1, -1, -1): # ডান -> বাঁ
r = rank[a[i]]
counts[i] = bit.prefix_sum(r - 1) # ডানে দেখা, আমার চেয়ে ছোট কয়টা
bit.add(r, 1) # নিজেকে frequency table-এ tick
return counts
def brute(a):
# obviously-correct O(n^2) reference
n = len(a)
res = [0] * n
for i in range(n):
for j in range(i + 1, n):
if a[j] < a[i]:
res[i] += 1
return res
# ---- tests ----
assert count_smaller([5, 2, 6, 1]) == [2, 1, 1, 0]
assert count_smaller([-1]) == [0]
assert count_smaller([-1, -1]) == [0, 0] # equal: strictly ছোট নয়
assert count_smaller([]) == [] # empty
assert count_smaller([1, 2, 3, 4]) == [0, 0, 0, 0] # increasing: ডানে কিছু ছোট নেই
assert count_smaller([4, 3, 2, 1]) == [3, 2, 1, 0] # decreasing: সব ডানেরটা ছোট
# BIT version সবসময় brute-এর সাথে একমত (duplicate, negative-সহ)
import random
for _ in range(400):
arr = [random.randint(-6, 6) for _ in range(random.randint(0, 15))]
assert count_smaller(arr) == brute(arr)
print("সব test pass!")
16. Line-by-line code explanation¶
Fenwick— standard BIT, এখানে value-দের frequency table; index = rank।order/rank— coordinate compression, value →1..k; বড়/negative value সামলায়।count_smaller—iকমার দিকে (ডান → বাঁ) হাঁটে;prefix_sum(r-1)দেয় ডানে-দেখা ছোট value-র সংখ্যা।r - 1— strictly ছোট চাই বলে নিজের সমান value বাদ।brute— O(n^2), নির্ভুল reference।
17. Output walkthrough¶
[5,2,6,1]-এ ডান-থেকে-বাঁ হেঁটে BIT version দেয় [2,1,1,0] (section 14)। তারপর 400-টা random array-তে — duplicate আর negative-সহ — brute-এর সাথে হুবহু মেলে। equal value ([-1,-1]) strictly ছোট নয় বলে [0,0]। শেষে print: সব test pass!।
18. Time complexity¶
O(n log n): প্রতি element-এ একটা prefix_sum + একটা add, প্রতিটা O(log n); compression-এর sort O(n log n)।
19. Space complexity¶
O(n): BIT array + rank map + output array।
20. Common mistakes¶
- BIT-এ index হিসেবে position নেওয়া — index মানে VALUE-র rank, এটাই মূল মোচড়।
- বাঁ-থেকে-ডান হাঁটা — তখন "ডানে কয়টা ছোট"-এর বদলে "বাঁয়ে কয়টা ছোট" পাও; reverse sweep জরুরি।
prefix_sum(r)লেখা যেখানেprefix_sum(r - 1)দরকার — equal value-ও গোনা হয়ে strict ভাঙে।- Coordinate compression বাদ — বড়/negative value-তে BIT index crash বা বিশাল array।
21. Which basic problem this inherits from¶
ভিত্তি: BIT-over-values (../fenwick-tree.md section 9) + sorting (compression)। সরাসরি বড় ভাই #10 (count inversions) — সেখানে মোট গোনা, এখানে per-element।
22. Similar harder problems¶
- Reverse Pairs (
a[i] > 2*a[j]শর্ত, একই reverse-sweep) — https://leetcode.com/problems/reverse-pairs/ (#14) - Count of Range Sum (prefix sum-এর উপর BIT) — https://leetcode.com/problems/count-of-range-sum/
- Salary Queries (compressed value-র উপর BIT, update-সহ) — CSES "Salary Queries" (#16)
23. Pattern learned¶
BIT + coordinate compression: value-দের একটা frequency table BIT-এ রাখো, array reverse করে হাঁটো, প্রতি element-এ prefix_sum(rank-1) জিজ্ঞেস করো। বড় value-কে rank-এ চেপে BIT ছোট রাখা — এই reflex অনেক counting problem-এ লাগবে।
24. Final summary¶
ভবিষ্যতের আমাকে: "ডানে কয়টা ছোট" = array উল্টে হেঁটে compressed-value frequency BIT-এ prefix_sum(rank-1)। #10-এর মোট-গোনার per-element রূপ। index = VALUE আর reverse sweep — এই দুই কথা মনে থাকলে #14, #16 স্রেফ এই idea-র টুইস্ট।
মনে রাখার নিয়ম (legal): সব নিজের ভাষায় লেখা; problem statement copy করা হয়নি; official link শুধু attribution-এর জন্য; "asked by Google/Amazon" দাবি নেই — এটা একটা common interview pattern।