Arrays and Strings — Problem-Solving Patterns¶
Pattern মানে আসলে চিন্তার একটা reusable আকার। Trigger word গুলো শেখো, core idea টা ধরো, আর সেই একটা mental tweak শেখো যেটা brute-force O(n^2) loop-কে একটা পরিষ্কার O(n) solution-এ বদলে দেয়।
Pattern 1 — Two Pointers¶
Trigger words: "pair that sums to...", "remove duplicates in place", "sorted array", "palindrome", "container / pair of walls", "move all zeros"।
Core idea: দুটো nested loop দিয়ে সব pair try করার (O(n^2)) বদলে দুটো index রাখো আর সেগুলোকে উদ্দেশ্য নিয়ে সরাও — সাধারণত দুই প্রান্ত থেকে ভেতরের দিকে, অথবা বাঁ দিক থেকে একটা slow আর একটা fast।
Thinking tweak: pointer-এর প্রতিটা move-এ কিছু candidate চিরতরে বাদ পড়তে হবে। তুমি যদি argue করতে পারো "L ডানে সরালে answer কখনো হারাতে পারি না", তাহলে তোমার হাতে একটা correct O(n) algorithm আছে।
Worked example — sorted [1, 3, 4, 6, 9]-এ 10-এ sum হয় এমন pair:
L=1, R=9 -> sum 10 FOUND.
(if sum had been < target: only moving L right can increase it;
if sum had been > target: only moving R left can decrease it —
each step kills one element forever, so at most n steps.)
Inherits from: সাধারণ array traversal + sorted-order invariant।
Representative problems:
- Sorted input-এ two-pointer pair sum — LeetCode 167
- Container-এর দেয়ালের সেরা pair — LeetCode 11
- সব zero-sum triple — LeetCode 15
Pattern 2 — Sliding Window¶
Trigger words: "longest/shortest substring বা subarray", "at most K distinct", "contains all characters of", "window of size k"।
Core idea: এমন একটা window [L..R] maintain করো যেটা সবসময় শর্ত মানে (বা মানার চেষ্টা করছে)। বড় করতে R expand করো; শর্ত ভাঙলেই শুধু L shrink করো। প্রতিটা index একবার ঢোকে একবার বের হয় → O(n)।
Thinking tweak: কিছু ভাঙলে কখনো window টা scratch থেকে restart করো না। বাঁ প্রান্ত sliding করে repair করো — মাঝের element গুলোর জন্য করা কাজটা এখনো valid।
Worked example — "abcabcbb"-এর repeat-ছাড়া longest substring:
grow: a -> ab -> abc (best = 3)
R hits second 'a': duplicate! slide L past the first 'a'
window becomes "bca", keep growing... best stays 3
Inherits from: two pointers (এটা আসলে একই দিকে চলা two pointers-ই) + window-র contents-এর জন্য frequency counting।
Representative problems:
- Repeating character ছাড়া longest substring — LeetCode 3
- t-এর সবকিছু cover করা minimum window — LeetCode 76
- Fixed size k-এর যেকোনো subarray-র max sum — classic fixed-window warm-up, দেখো USACO Guide-এর sliding-window module
Pattern 3 — Prefix Sums¶
Trigger words: "sum of range [l, r]", "many queries", "count subarrays with sum K", "equilibrium / pivot index"।
Core idea: আগে থেকে হিসাব করে রাখো prefix[i] = প্রথম i-টা element-এর sum। তাহলে যেকোনো range sum নেমে আসে একটা subtraction-এ: sum(l..r) = prefix[r+1] - prefix[l]।
Thinking tweak: "subarray sum equals K" হয়ে যায় "আমি কি আগে current_prefix - K সমান কোনো prefix দেখেছি?" — দেখা prefix গুলো একটা hash map-এ রাখো, আর এক pass-এই এমন সব subarray count হয়ে যাবে।
Worked example — [1, 2, 1]-এর কয়টা subarray-র sum 3:
prefixes seen: {0:1}
running=1 -> need 1-3=-2, absent. record 1.
running=3 -> need 3-3=0, seen once! count=1. record 3.
running=4 -> need 4-3=1, seen once! count=2. answer: 2 ([1,2] and [2,1])
Inherits from: prefix/difference/contribution chapter — পুরো derivation আর difference-array জমজটার জন্য দেখো ../01-math-based-programming-fundamentals/05-prefix-difference-contribution/।
Representative problems:
- Subarray sum equals K — LeetCode 560
- Product of array except self (prefix products) — LeetCode 238
- Static Range Sum Queries — CSES Problem Set, task "Static Range Sum Queries"
Pattern 4 — In-Place Reversal and Rotation¶
Trigger words: "rotate by k", "in place", "O(1) extra space", "reverse words/sections"।
Core idea: Two pointers দিয়ে reversal-এর খরচ O(n) time আর O(1) space। k দিয়ে rotation মানে তিনটা reversal: পুরো array reverse করো, তারপর প্রথম k-টা reverse করো, তারপর বাকিটা reverse করো।
Thinking tweak: rotation শুনলে মনে হয় একটা temporary array লাগবেই — কিন্তু দুইবার reverse করলে প্রতিটা block আবার forward order-এ ফিরে আসে, অথচ block দুটো নিজেদের মধ্যে জায়গা বদলে ফেলেছে।
Worked example — [1, 2, 3, 4, 5] কে ডানে k = 2 rotate করো:
reverse all: [5, 4, 3, 2, 1]
reverse first 2: [4, 5, 3, 2, 1]
reverse last 3: [4, 5, 1, 2, 3] done — the last two items moved to the front
Inherits from: Pattern 1-এর two-pointer swap।
Representative problems:
- Rotate array — LeetCode 189
- In place string reverse — LeetCode 344
- String-এর word গুলো reverse — LeetCode 151
Pattern 5 — Frequency Counting¶
Trigger words: "anagram", "first unique", "majority", "appears exactly/at least k times", "permutation of each other"।
Core idea: এক O(n) pass-এ একটা dict-এ (বা collections.Counter-এ) occurrence count করো, তারপর বারবার re-scan না করে count গুলো থেকেই প্রশ্নের উত্তর দাও।
Thinking tweak: দুটো string anagram iff তাদের count map সমান — arrangement না, summary compare করো। Lowercase letter-এর জন্য 26-slot-এর একটা array আরো সস্তা counter।
Worked example — "listen" কি "silent"-এর anagram?
count("listen") = {l:1, i:1, s:1, t:1, e:1, n:1}
count("silent") = {s:1, i:1, l:1, e:1, n:1, t:1}
maps equal -> YES, in O(n), without trying any of the n! orderings
Inherits from: সাধারণ traversal + hash-map idea (constant-time lookup)।
Representative problems:
- Valid anagram — LeetCode 242
- Canonical key দিয়ে group anagrams — LeetCode 49
- Top k frequent elements — LeetCode 347
Pattern 6 — String Building (O(n^2) trap এড়াও)¶
Trigger words: "construct the result string", "transform/encode/decode", "repeat n times", যেকোনো loop যেটা একটা string বাড়াচ্ছে।
Core idea: string immutable, তাই s += piece প্রতিবার পুরো s copy করে — ওরকম একটা loop মানে O(n^2)। টুকরোগুলো একটা list-এ জমাও আর শেষে একবার ''.join করো: O(n)।
Thinking tweak: answer string-টাকে একদম শেষ মুহূর্ত পর্যন্ত frozen ধরে নাও। Final join-এর আগ পর্যন্ত সবকিছু স্রেফ fragment-এর একটা list।
Worked example — "aaabb"-এর run-length encode:
parts = []
run 'a' x3 -> parts = ["a", "3"]
run 'b' x2 -> parts = ["a", "3", "b", "2"]
"".join(parts) -> "a3b2" every character written exactly once
Inherits from: dynamic array-র append-is-cheap property (চাইলে একে Pattern 0 বলতে পারো)।
Representative problems:
- In place string compression — LeetCode 443
- Zigzag conversion (row বানাও, join করো) — LeetCode 6
3[a2[c]]-এর মতো string decode — LeetCode 394 (এটা../04-stack-and-queue/-এর stack pattern-এরও একটা preview)
Pattern 7 — Kadane's Algorithm (maximum subarray)¶
Trigger words: "maximum sum of a contiguous subarray", "best segment", "maximum product subarray" (variant)।
Core idea: বাঁ থেকে ডানে হাঁটো আর রাখো best_ending_here = current index-এ শেষ হওয়া সেরা subarray। প্রতিটা element-এ choose করো: আগের run টা extend করবে, নাকি এখান থেকে fresh শুরু করবে।
Thinking tweak: negative running total মানে মরা ওজন — সামনে যা-ই আসুক সেটাকে শুধু টেনে নামাবে, তাই ওটা ফেলে দাও আর restart করো। প্রতি element-এ একটা local decision-ই global answer দিয়ে দেয়।
Worked example — [-2, 1, -3, 4, -1, 2, 1]:
x=-2: end=max(-2, ...)=-2 best=-2
x= 1: prev end -2 < 0 -> restart: end=1 best=1
x=-3: end=1-3=-2 best=1
x= 4: prev end -2 < 0 -> restart: end=4 best=4
x=-1: end=4-1=3 best=4
x= 2: end=3+2=5 best=5
x= 1: end=5+1=6 best=6 -> answer 6 (subarray [4,-1,2,1])
Inherits from: prefix-sum চিন্তা — best ending at i মানে prefix[i] - min(prefix[0..i-1]), আর Kadane's সেই minimum-টাই implicitly track করে। অনেকের জীবনের প্রথম dynamic-programming recurrence-ও এটাই।
Representative problems:
- Maximum subarray — LeetCode 53
- Best time to buy and sell stock (running-min variant) — LeetCode 121
- Maximum product subarray (min EBONG max track করো) — LeetCode 152
Pattern cheat sheet¶
| Pattern | Killer trigger | Brute force-কে হারায় কারণ |
|---|---|---|
| Two pointers | "pair/triple in sorted data", "in place" | প্রতিটা move candidate চিরতরে বাদ দেয় |
| Sliding window | "longest/shortest sub*" | restart না, repair করো |
| Prefix sums | "range sum", "subarrays summing to K" | একবার precompute, উত্তর O(1)-এ |
| Reversal/rotation | "rotate by k, O(1) space" | তিনটা reversal temp array-র জায়গা নেয় |
| Frequency counting | "anagram/unique/majority" | arrangement না, summary compare |
| String building | string বাড়ানো যেকোনো loop | join প্রতিটা char একবারই copy করে, n বার না |
| Kadane's | "max contiguous sum" | negative prefix greedily ফেলে দাও |
এবার implementation.py-র from-scratch build গুলো পড়ো, তারপর problems/README.md-তে tracker শুরু করো।