A complete A→Z Python DSA study site for interviews: 23 topics, static visual explanations, reusable templates, a 40-day plan, LeetCode practice and browser-saved progress — without a heavy runtime.
The original traced-Python lab was intentionally removed to keep this GitHub Pages project small, fast and dependable. The teaching content, diagrams, progress tracking and practice workflow remain.
| # | Topic | Hours | Days | Practice |
|---|---|---|---|---|
| 01 | Python for DSA & Online Tests | 3h | 1 | 6 |
| 02 | Big-O & Complexity | 3h | 2 | 5 |
| 03 | Arrays, Strings & Prefix Sums | 9h | 3–5 | 14 |
| 04 | Hashing: dict, set & Counter | 6h | 6–7 | 11 |
| 05 | Two Pointers | 5h | 8–9 | 12 |
| 06 | Sliding Window | 6h | 9–11 | 10 |
| 07 | Stacks & Monotonic Stack | 5h | 11–13 | 10 |
| 08 | Queues & Deques | 2h | 13 | 5 |
| 09 | Linked Lists | 6h | 14–15 | 12 |
| 10 | Recursion | 4h | 16–17 | 5 |
| 11 | Backtracking | 6h | 17–19 | 12 |
| 12 | Sorting | 5h | 19–20 | 10 |
| 13 | Binary Search | 6h | 21–22 | 11 |
| 14 | Binary Trees | 8h | 23–25 | 15 |
| 15 | Binary Search Trees | 3h | 25–26 | 7 |
| 16 | Heaps & Priority Queues | 4h | 26–27 | 7 |
| 17 | Graphs: BFS, DFS, Topological Sort & Union-Find | 10h | 28–31 | 16 |
| 18 | Shortest Paths & Minimum Spanning Trees | 3h | 31–32 | 6 |
| 19 | Greedy & Intervals | 4h | 32–33 | 10 |
| 20 | Dynamic Programming | 13h | 33–37 | 21 |
| 21 | Tries (Prefix Trees) | 2h | 38 | 5 |
| 22 | Bit Manipulation & Math | 2h | 38–39 | 9 |
| 23 | Mock Interviews & Final Review | 5h | 39–40 | 9 |
Progress is stored in this browser. Export a JSON backup before clearing browser storage or changing devices.
About one third concept/visual review, one third coding templates and examples, one third core problem solving. On large topics, the split naturally carries across several days.
A commonly reported flow across Brain Station 23 and Therap BD is written/online screening (CS fundamentals + logical reasoning), then timed DSA/live coding or take-home, technical interviews and HR. A typical full timeline is about three weeks.
Toptal uses a timed Codility algorithmic test (commonly 90–120 minutes, 3–4 problems) followed by live-coding video stages. The same disciplined DSA workflow applies.
BD Programmers is problem-solving focused, and Python User Group Bangladesh is useful if Python is your interview language. Record yourself solving a problem aloud: it exposes unclear reasoning that silent practice hides.
Think of Python as a workshop. A list, dict, set, deque and heap are different tools; using the wrong one can turn a clean O(n) idea into an O(n²) program.
Use these idioms as a starting point, then adapt them to the problem's exact constraints and return format.
from collections import Counter, defaultdict, deque
import heapq, bisect
# fast-ish stdin pattern
import sys
it = iter(sys.stdin.buffer.read().split())
n = int(next(it))
nums = [int(next(it)) for _ in range(n)]
# independent matrix rows
grid = [[0] * 4 for _ in range(3)]
# stable sort by two keys
rows.sort(key=lambda r: (r[0], -r[1]))
Signals: counting · duplicates · most common
from collections import Counter
freq = Counter(nums)
answer = max(freq, key=freq.get)Signals: group records · anagrams · buckets
from collections import defaultdict
groups = defaultdict(list)
for item in items:
groups[key(item)].append(item)Signals: stdin · multiple tests
import sys
input = sys.stdin.readline
for _ in range(int(input())):
n = int(input())
a = list(map(int, input().split()))
print(solve(a))Use a list comprehension for independent rows. Python assignment binds names to objects; it does not copy nested objects.
Brute-force instinct
Creating a matrix with [[0]*m]*n looks compact but repeats the same row reference. A write to one row appears in every row. Use a comprehension so each row is allocated separately.
Pattern-level solution
The optimized approach follows the invariant taught above and removes repeated work or impossible candidates.
How to say it in an interview: "I want independent mutable rows, so I allocate each row inside the comprehension; repetition would alias the same object."
| Operation / pattern | Time | Space / note |
|---|---|---|
| list append | amortized O(1) | dynamic-array growth |
| list insert/pop(0) | O(n) | shifts elements |
| dict/set lookup | average O(1) | hashing |
| slice a[l:r] | O(k) | copies k items |
| sorted | O(n log n) | Timsort, stable |
In Bangladesh-style timed assessments and live coding, recognise the pattern quickly, state the target complexity before coding, and test boundary cases. Python is typically acceptable because hiring is usually language-agnostic. Practise explaining the solution aloud and record yourself periodically.
| # | Problem | Level | Key idea | |
|---|---|---|---|---|
| 1 | Two Sum core | Easy | dict value → index; check complement before insert | |
| 217 | Contains Duplicate core | Easy | len(set(nums)) or a seen set | |
| 242 | Valid Anagram core | Easy | Compare Counter/frequency maps | |
| 49 | Group Anagrams core | Medium | Group by a canonical key such as 26 counts | |
| 347 | Top K Frequent Elements | Medium | Counter + heap/buckets | |
| 128 | Longest Consecutive Sequence | Medium | Start only from numbers whose predecessor is absent |
Imagine the input doubles. If the work doubles, that is linear. If it quadruples, think quadratic. If it adds one extra step, think logarithmic.
Use these idioms as a starting point, then adapt them to the problem's exact constraints and return format.
# Count actual operations to build intuition
ops = 0
seen = set()
for x in nums:
ops += 1
if x in seen:
return True
seen.add(x)
return False
Signals: single pass · all items
for x in nums:
use(x)Signals: sorted search · divide by 2
while n > 1:
n //= 2Signals: all pairs · matrix relation
for i in range(n):
for j in range(i + 1, n):
check(i, j)Constraints decide the target complexity before you code. Around 10⁵ inputs usually demands near-linear or n log n work, not quadratic work.
Brute-force instinct
A double loop compares every pair in O(n²). A set stores previously seen values, changing membership checks to average O(1) and the full algorithm to O(n).
Pattern-level solution
The optimized approach follows the invariant taught above and removes repeated work or impossible candidates.
How to say it in an interview: "The constraint rules out pairwise comparison, so I trade O(n) extra memory for an O(n) seen-set scan."
| Operation / pattern | Time | Space / note |
|---|---|---|
| hash scan | O(n) | O(n) |
| sort + scan | O(n log n) | O(1)–O(n) implementation-dependent |
| binary search | O(log n) | O(1) |
| naive Fibonacci | O(2^n) | O(n) stack |
| memo Fibonacci | O(n) | O(n) |
In Bangladesh-style timed assessments and live coding, recognise the pattern quickly, state the target complexity before coding, and test boundary cases. Python is typically acceptable because hiring is usually language-agnostic. Practise explaining the solution aloud and record yourself periodically.
| # | Problem | Level | Key idea | |
|---|---|---|---|---|
| 704 | Binary Search core | Easy | Halve the search interval | |
| 69 | Sqrt(x) core | Easy | Binary search the integer answer | |
| 509 | Fibonacci Number core | Easy | Compare exponential recursion with memo/iteration | |
| 35 | Search Insert Position | Easy | Lower-bound style binary search | |
| 278 | First Bad Version | Easy | First true in a monotonic predicate |
An array is a row of labeled lockers. Indexing jumps directly to a locker; insertion in the middle forces everything after it to shift.
Use these idioms as a starting point, then adapt them to the problem's exact constraints and return format.
# 1D prefix sums
pref = [0]
for x in nums:
pref.append(pref[-1] + x)
def range_sum(l, r):
return pref[r + 1] - pref[l]
# Kadane
best = cur = nums[0]
for x in nums[1:]:
cur = max(x, cur + x)
best = max(best, cur)
Signals: many range sums · cumulative
pref = [0]
for x in nums: pref.append(pref[-1] + x)
ans = pref[r+1] - pref[l]Signals: maximum contiguous sum
cur = best = nums[0]
for x in nums[1:]:
cur = max(x, cur + x)
best = max(best, cur)Signals: all-except-self · no division
out = [1] * n
p = 1
for i in range(n):
out[i] = p; p *= nums[i]
s = 1
for i in range(n-1, -1, -1):
out[i] *= s; s *= nums[i]Signals: spiral · layers
top,left,bottom,right = 0,0,m-1,n-1
while top <= bottom and left <= right:
# traverse top/right/bottom/left, then shrinkPrecompute cumulative totals once; each later range-sum query becomes O(1). The same idea extends to 2D rectangles and difference arrays.
Brute-force instinct
Enumerating every subarray and summing it is O(n³), or O(n²) with prefix sums. Kadane observes that only the best sum ending at i-1 matters for extending into i, reducing the state to one number and the time to O(n).
Pattern-level solution
The optimized approach follows the invariant taught above and removes repeated work or impossible candidates.
How to say it in an interview: "At each index I either start a new subarray here or extend the best one ending immediately before it; that local recurrence is sufficient."
| Operation / pattern | Time | Space / note |
|---|---|---|
| index read/write | O(1) | O(1) |
| append | amortized O(1) | O(1) |
| insert/delete middle | O(n) | O(1) |
| prefix build | O(n) | O(n) |
| Kadane | O(n) | O(1) |
| matrix traversal | O(mn) | O(1) extra possible |
In Bangladesh-style timed assessments and live coding, recognise the pattern quickly, state the target complexity before coding, and test boundary cases. Python is typically acceptable because hiring is usually language-agnostic. Practise explaining the solution aloud and record yourself periodically.
| # | Problem | Level | Key idea | |
|---|---|---|---|---|
| 121 | Best Time to Buy and Sell Stock core | Easy | Track minimum price so far and best profit | |
| 53 | Maximum Subarray core | Medium | Kadane: best ending here | |
| 238 | Product of Array Except Self core | Medium | Prefix product then suffix product | |
| 169 | Majority Element core | Easy | Boyer–Moore candidate cancellation | |
| 303 | Range Sum Query - Immutable core | Easy | Prefix sum with leading zero | |
| 304 | Range Sum Query 2D - Immutable core | Medium | 2D prefix + inclusion exclusion | |
| 54 | Spiral Matrix core | Medium | Shrink four boundaries | |
| 48 | Rotate Image core | Medium | Transpose then reverse each row | |
| 73 | Set Matrix Zeroes core | Medium | Use first row/column as markers | |
| 189 | Rotate Array core | Medium | Reverse whole + segments or cyclic replacement | |
| 14 | Longest Common Prefix core | Easy | Compare columns or shrink prefix | |
| 151 | Reverse Words in a String core | Medium | split/filter/reverse/join or in-place language-specific | |
| 918 | Maximum Sum Circular Subarray | Medium | max normal vs total - min subarray | |
| 41 | First Missing Positive | Hard | Place value x at index x-1 in place |
A hash table is a wall of mailboxes. The hash function chooses the mailbox; collisions mean several letters share a box, so the key is still checked.
Use these idioms as a starting point, then adapt them to the problem's exact constraints and return format.
from collections import Counter, defaultdict
# prefix sum + count map
count = {0: 1}
prefix = ans = 0
for x in nums:
prefix += x
ans += count.get(prefix - k, 0)
count[prefix] = count.get(prefix, 0) + 1
Signals: pair sum · lookup missing value
seen = {}
for i, x in enumerate(nums):
if target - x in seen:
return [seen[target-x], i]
seen[x] = iSignals: counts · anagram · top-k
from collections import Counter
freq = Counter(items)Signals: subarray sum = k · count ranges
freq = {0:1}; p=ans=0
for x in nums:
p += x
ans += freq.get(p-k, 0)
freq[p] = freq.get(p,0)+1Average O(1) lookup comes from hashing a key to a bucket. Collisions are normal; equality checks inside a bucket resolve them.
Brute-force instinct
A brute-force O(n²) enumeration can compute every range. Using prefix sums, a subarray ending now sums to k exactly when an earlier prefix equals current-k; a frequency map counts all such earlier prefixes in O(1) average time.
Pattern-level solution
The optimized approach follows the invariant taught above and removes repeated work or impossible candidates.
How to say it in an interview: "I store how many times each prefix sum occurred. For current prefix p, every previous p-k creates one valid subarray ending here."
| Operation / pattern | Time | Space / note |
|---|---|---|
| lookup/insert/delete | average O(1) | O(n) |
| frequency build | O(n) | O(n) |
| group anagrams | O(total chars) with count key | O(total chars) |
| longest consecutive | O(n) average | O(n) |
| prefix-hash subarrays | O(n) | O(n) |
In Bangladesh-style timed assessments and live coding, recognise the pattern quickly, state the target complexity before coding, and test boundary cases. Python is typically acceptable because hiring is usually language-agnostic. Practise explaining the solution aloud and record yourself periodically.
| # | Problem | Level | Key idea | |
|---|---|---|---|---|
| 1 | Two Sum core | Easy | Complement lookup in a dict | |
| 217 | Contains Duplicate core | Easy | Seen set | |
| 242 | Valid Anagram core | Easy | Counter or fixed 26-count array | |
| 49 | Group Anagrams core | Medium | Canonical count tuple as key | |
| 128 | Longest Consecutive Sequence core | Medium | Expand only from sequence starts | |
| 560 | Subarray Sum Equals K core | Medium | Prefix-sum frequency map | |
| 219 | Contains Duplicate II core | Easy | Last index map or bounded window set | |
| 36 | Valid Sudoku core | Medium | Sets for row, column and box constraints | |
| 705 | Design HashSet | Easy | Buckets + collision handling | |
| 706 | Design HashMap | Easy | Buckets storing key/value pairs | |
| 380 | Insert Delete GetRandom O(1) | Medium | Array + value→index map |
Put one finger on each end of a sorted row. If the sum is too small move the left finger to a larger value; if too large move the right finger to a smaller value.
Use these idioms as a starting point, then adapt them to the problem's exact constraints and return format.
def pair_sum_sorted(nums, target):
l, r = 0, len(nums)-1
while l < r:
s = nums[l] + nums[r]
if s == target: return [l, r]
if s < target: l += 1
else: r -= 1
return []
Signals: sorted pair · palindrome · container
l, r = 0, len(a)-1
while l < r:
if need_bigger: l += 1
else: r -= 1Signals: in place · remove/keep · compact
w = 0
for r in range(len(a)):
if keep(a[r]):
a[w] = a[r]; w += 1Signals: 3Sum · triplets
nums.sort()
for i in range(n-2):
l, r = i+1, n-1
while l < r: ...On sorted data, one comparison can discard an entire set of pairs. Each pointer moves at most n times, giving O(n).
Brute-force instinct
Brute force checks every triplet in O(n³). Sorting allows each fixed first value to reduce the remainder to sorted Two Sum in O(n), producing O(n²) overall. Duplicate skipping avoids repeated answers.
Pattern-level solution
The optimized approach follows the invariant taught above and removes repeated work or impossible candidates.
How to say it in an interview: "Sorting costs n log n, but the dominant cost is n linear two-pointer scans, so O(n²). Sorting also lets me eliminate duplicate triplets by comparing neighbours."
| Operation / pattern | Time | Space / note |
|---|---|---|
| sorted pair sum | O(n) | O(1) |
| 3Sum | O(n²) | O(1) extra apart from sort/output |
| valid palindrome | O(n) | O(1) |
| in-place compaction | O(n) | O(1) |
| merge sorted sequences | O(n+m) | O(n+m) output |
In Bangladesh-style timed assessments and live coding, recognise the pattern quickly, state the target complexity before coding, and test boundary cases. Python is typically acceptable because hiring is usually language-agnostic. Practise explaining the solution aloud and record yourself periodically.
| # | Problem | Level | Key idea | |
|---|---|---|---|---|
| 125 | Valid Palindrome core | Easy | Skip non-alphanumerics from both ends | |
| 344 | Reverse String core | Easy | Swap ends inward | |
| 26 | Remove Duplicates from Sorted Array core | Easy | Read/write pointer | |
| 283 | Move Zeroes core | Easy | Compact nonzero values | |
| 977 | Squares of a Sorted Array core | Easy | Fill result from largest absolute end | |
| 167 | Two Sum II - Input Array Is Sorted core | Medium | Move left/right by comparison | |
| 15 | 3Sum core | Medium | Sort + fixed index + pair scan | |
| 11 | Container With Most Water | Medium | Move the shorter wall | |
| 392 | Is Subsequence | Easy | Two sequence pointers | |
| 88 | Merge Sorted Array | Easy | Fill from the back | |
| 680 | Valid Palindrome II | Easy | Try one skip after first mismatch | |
| 42 | Trapping Rain Water | Hard | Two sides with running maxima |
Imagine a camera frame sliding over a long banner. Instead of recounting everything visible after each move, subtract what leaves and add what enters.
Use these idioms as a starting point, then adapt them to the problem's exact constraints and return format.
from collections import deque
# variable window: longest without repeats
seen = set(); l = best = 0
for r, ch in enumerate(s):
while ch in seen:
seen.remove(s[l]); l += 1
seen.add(ch)
best = max(best, r-l+1)
Signals: exact k · moving average/sum
cur = sum(a[:k]); best = cur
for r in range(k, len(a)):
cur += a[r] - a[r-k]
best = max(best, cur)Signals: longest substring/subarray under rule
l = 0
for r, x in enumerate(a):
add(x)
while invalid():
remove(a[l]); l += 1
best = max(best, r-l+1)Signals: window max/min
dq = deque()
for i, x in enumerate(a):
while dq and a[dq[-1]] <= x: dq.pop()
dq.append(i)
if dq[0] <= i-k: dq.popleft()Expand right to gain information; shrink left only when the window violates the rule. Every index enters and leaves at most once, so variable windows are usually O(n).
Brute-force instinct
Checking every substring and uniqueness is O(n²) or worse. A set plus left pointer maintains a duplicate-free window; when s[r] repeats, remove from the left until valid, then update the best length.
Pattern-level solution
The optimized approach follows the invariant taught above and removes repeated work or impossible candidates.
How to say it in an interview: "The window invariant is that all characters are unique. Right expands once per character and left only moves forward, so total work is O(n)."
| Operation / pattern | Time | Space / note |
|---|---|---|
| fixed window | O(n) | O(1) or state size |
| variable window | O(n) | O(alphabet/state) |
| frequency compare | O(n) | O(alphabet) |
| sliding max deque | O(n) | O(k) |
| brute all windows | O(nk) or O(n²) | varies |
In Bangladesh-style timed assessments and live coding, recognise the pattern quickly, state the target complexity before coding, and test boundary cases. Python is typically acceptable because hiring is usually language-agnostic. Practise explaining the solution aloud and record yourself periodically.
| # | Problem | Level | Key idea | |
|---|---|---|---|---|
| 643 | Maximum Average Subarray I core | Easy | Fixed k running sum | |
| 3 | Longest Substring Without Repeating Characters core | Medium | Variable window + set/last index | |
| 209 | Minimum Size Subarray Sum core | Medium | Shrink after sum reaches target | |
| 424 | Longest Repeating Character Replacement core | Medium | Window size - max frequency <= k | |
| 567 | Permutation in String core | Medium | Fixed window frequency equality | |
| 1004 | Max Consecutive Ones III core | Medium | At most k zeros | |
| 76 | Minimum Window Substring core | Hard | Need/have counts; shrink while satisfied | |
| 239 | Sliding Window Maximum core | Hard | Decreasing deque of indices | |
| 438 | Find All Anagrams in a String | Medium | Fixed window frequency map | |
| 1456 | Maximum Number of Vowels in a Substring of Given Length | Medium | Fixed k count |
A stack is a pile of plates: only the top is directly accessible. That LIFO order exactly matches nested brackets and unfinished work.
Use these idioms as a starting point, then adapt them to the problem's exact constraints and return format.
# next greater index to the right
stack = []
ans = [-1] * len(nums)
for i, x in enumerate(nums):
while stack and nums[stack[-1]] < x:
j = stack.pop()
ans[j] = i
stack.append(i)
Signals: nested delimiters · matching
pairs = {')':'(',']':'[','}':'{'}
st=[]
for ch in s:
if ch in pairs.values(): st.append(ch)
elif not st or st.pop()!=pairs[ch]: return FalseSignals: next greater/smaller · span
st=[]
for i,x in enumerate(a):
while st and a[st[-1]] < x:
j=st.pop(); answer[j]=i
st.append(i)Signals: largest rectangle · nearest smaller
st=[]
for i,h in enumerate(heights+[0]):
while st and heights[st[-1]] > h:
j=st.pop(); left=st[-1] if st else -1
best=max(best, heights[j]*(i-left-1))
st.append(i)When a new value resolves older candidates, pop them. Each index is pushed and popped at most once: amortized O(n).
Brute-force instinct
A brute scan looks right from every day, O(n²). A decreasing stack keeps only days still waiting for a warmer temperature. Each day is pushed once and popped once, making O(n).
Pattern-level solution
The optimized approach follows the invariant taught above and removes repeated work or impossible candidates.
How to say it in an interview: "The stack contains unresolved indices in decreasing temperature order. Today resolves every colder day on top."
| Operation / pattern | Time | Space / note |
|---|---|---|
| push/pop/top | O(1) | O(n) |
| valid parentheses | O(n) | O(n) |
| RPN | O(n) | O(n) |
| monotonic next greater | O(n) amortized | O(n) |
| largest rectangle | O(n) | O(n) |
In Bangladesh-style timed assessments and live coding, recognise the pattern quickly, state the target complexity before coding, and test boundary cases. Python is typically acceptable because hiring is usually language-agnostic. Practise explaining the solution aloud and record yourself periodically.
| # | Problem | Level | Key idea | |
|---|---|---|---|---|
| 20 | Valid Parentheses core | Easy | Match closers to stack top | |
| 155 | Min Stack core | Medium | Store running minimum per push | |
| 150 | Evaluate Reverse Polish Notation core | Medium | Stack operands; preserve operand order | |
| 739 | Daily Temperatures core | Medium | Decreasing stack of unresolved indices | |
| 496 | Next Greater Element I core | Easy | Monotonic stack + map result | |
| 84 | Largest Rectangle in Histogram core | Hard | Increasing stack; pop on lower height | |
| 394 | Decode String | Medium | Stack nested count/string state | |
| 735 | Asteroid Collision | Medium | Resolve collisions against stack top | |
| 901 | Online Stock Span | Medium | Decreasing stack of price/span pairs | |
| 71 | Simplify Path | Medium | Stack path components |
A checkout line serves the oldest waiting person first. A deque adds a second door, so items can efficiently enter or leave from either end.
Use these idioms as a starting point, then adapt them to the problem's exact constraints and return format.
from collections import deque
q = deque([1, 2, 3])
q.append(4) # back
x = q.popleft() # front
Signals: BFS · tasks · stream
from collections import deque
q=deque([start])
while q:
node=q.popleft()
for nei in graph[node]: ...Signals: fixed capacity · wraparound
tail = (head + size) % cap
buf[tail] = value
size += 1Signals: implement queue using stacks
if not out:
while inp: out.append(inp.pop())
return out.pop()Use collections.deque for O(1) append and popleft. A Python list with pop(0) shifts remaining elements and costs O(n).
Brute-force instinct
Keeping every timestamp and rescanning is unnecessary. Because timestamps arrive in order, append the new time and remove old times from the front until the first is within the window.
Pattern-level solution
The optimized approach follows the invariant taught above and removes repeated work or impossible candidates.
How to say it in an interview: "The deque contains exactly the active time interval; each timestamp enters and leaves once, so calls are amortized O(1)."
| Operation / pattern | Time | Space / note |
|---|---|---|
| deque append/popleft | O(1) | O(n) |
| list pop(0) | O(n) | O(n) |
| circular queue enqueue/dequeue | O(1) | O(capacity) |
| two-stack queue | amortized O(1) | O(n) |
In Bangladesh-style timed assessments and live coding, recognise the pattern quickly, state the target complexity before coding, and test boundary cases. Python is typically acceptable because hiring is usually language-agnostic. Practise explaining the solution aloud and record yourself periodically.
| # | Problem | Level | Key idea | |
|---|---|---|---|---|
| 232 | Implement Queue using Stacks core | Easy | Lazy transfer between input/output stacks | |
| 933 | Number of Recent Calls core | Easy | Deque timestamps; evict older than t-3000 | |
| 622 | Design Circular Queue core | Medium | Fixed array + head + size + modulo | |
| 1823 | Find the Winner of the Circular Game | Medium | Queue/deque simulation or Josephus recurrence | |
| 649 | Dota2 Senate | Medium | Two queues of future indices |
Think of train cars connected by couplers. To reorder the train you change links between cars; there is no direct jump to the 50th car.
Use these idioms as a starting point, then adapt them to the problem's exact constraints and return format.
def reverse(head):
prev, cur = None, head
while cur:
nxt = cur.next
cur.next = prev
prev, cur = cur, nxt
return prev
Signals: head may change · merge/delete
dummy = ListNode(0, head)
prev = dummy
# mutate through prev.next
return dummy.nextSignals: middle · cycle · palindrome
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.nextSignals: nth from end
dummy=ListNode(0,head); fast=slow=dummy
for _ in range(n+1): fast=fast.next
while fast: fast=fast.next; slow=slow.next
slow.next=slow.next.nextprev ← curr → next becomes prev ← curr, then advance.
A dummy node simplifies head-edge cases. Fast/slow pointers solve middle, cycle and palindrome patterns without extra arrays.
Brute-force instinct
Copying values into an array and rebuilding uses O(n) extra space. Rewiring next pointers needs only prev/curr/next and O(1) extra space.
Pattern-level solution
The optimized approach follows the invariant taught above and removes repeated work or impossible candidates.
How to say it in an interview: "Before changing curr.next I save the original next node; then I reverse one edge and advance both pointers."
| Operation / pattern | Time | Space / note |
|---|---|---|
| access kth node | O(k) | O(1) |
| reverse | O(n) | O(1) |
| cycle detection | O(n) | O(1) |
| merge sorted | O(n+m) | O(1) |
| LRU get/put | O(1) | O(capacity) |
In Bangladesh-style timed assessments and live coding, recognise the pattern quickly, state the target complexity before coding, and test boundary cases. Python is typically acceptable because hiring is usually language-agnostic. Practise explaining the solution aloud and record yourself periodically.
| # | Problem | Level | Key idea | |
|---|---|---|---|---|
| 206 | Reverse Linked List core | Easy | prev/curr/nxt rewiring | |
| 21 | Merge Two Sorted Lists core | Easy | Dummy head + tail | |
| 141 | Linked List Cycle core | Easy | Floyd slow/fast meeting | |
| 142 | Linked List Cycle II core | Medium | Reset one pointer to head after meeting | |
| 19 | Remove Nth Node From End of List core | Medium | Dummy + n+1 gap | |
| 234 | Palindrome Linked List core | Easy | Reverse second half and compare | |
| 160 | Intersection of Two Linked Lists core | Easy | Switch heads to equalize path lengths | |
| 143 | Reorder List core | Medium | Middle + reverse + weave | |
| 2 | Add Two Numbers | Medium | Digit-wise carry with dummy tail | |
| 138 | Copy List with Random Pointer | Medium | Old→new map or interweaving nodes | |
| 146 | LRU Cache | Medium | Dict + doubly linked list | |
| 25 | Reverse Nodes in k-Group | Hard | Reverse each full k-block |
A recursive function delegates a smaller version of the same job to a future copy of itself, then finishes its own work when that copy returns.
Use these idioms as a starting point, then adapt them to the problem's exact constraints and return format.
from functools import cache
@cache
def fib(n):
if n < 2: return n
return fib(n-1) + fib(n-2)
def power(x, n):
if n == 0: return 1
half = power(x, n//2)
return half*half if n%2==0 else x*half*half
Signals: halve input · exponentiation
def power(x,n):
if n==0: return 1
h=power(x,n//2)
return h*h if n%2==0 else x*h*hSignals: overlapping subproblems
from functools import cache
@cache
def f(state):
if base(state): return base_value
return combine(f(next_state))Signals: avoid recursion depth
stack=[start]
while stack:
state=stack.pop()
# push children in reverse desired orderDefine the base case, trust the smaller call, and combine its result. Memoization collapses repeated subtrees.
Brute-force instinct
Naive recursion branches twice and recomputes the same fib(k) many times, causing exponential work. Cache each n so every state is solved once: O(n) time and O(n) memory.
Pattern-level solution
The optimized approach follows the invariant taught above and removes repeated work or impossible candidates.
How to say it in an interview: "The recurrence is fine; the problem is repeated states. Memoization stores each fib(n) once, collapsing exponential recomputation to linear."
| Operation / pattern | Time | Space / note |
|---|---|---|
| factorial recursion | O(n) | O(n) stack |
| naive Fibonacci | O(2^n) | O(n) stack |
| memo Fibonacci | O(n) | O(n) |
| fast power | O(log n) | O(log n) stack |
| iterative DFS equivalent | O(V+E) | O(V) |
In Bangladesh-style timed assessments and live coding, recognise the pattern quickly, state the target complexity before coding, and test boundary cases. Python is typically acceptable because hiring is usually language-agnostic. Practise explaining the solution aloud and record yourself periodically.
| # | Problem | Level | Key idea | |
|---|---|---|---|---|
| 509 | Fibonacci Number core | Easy | Compare naive recursion, cache and iteration | |
| 50 | Pow(x, n) core | Medium | Exponentiation by squaring | |
| 344 | Reverse String core | Easy | Recursive swap from ends inward | |
| 24 | Swap Nodes in Pairs core | Medium | Recursively solve rest then swap first pair | |
| 779 | K-th Symbol in Grammar | Medium | Recurse by parent relation/parity |
You are exploring a maze with a pencil. At each junction choose a route, mark it, explore, then erase that choice when you return to try another route.
Use these idioms as a starting point, then adapt them to the problem's exact constraints and return format.
def subsets(nums):
out, path = [], []
def dfs(i):
if i == len(nums):
out.append(path[:]); return
path.append(nums[i]); dfs(i+1); path.pop()
dfs(i+1)
dfs(0)
return out
Signals: include/exclude · all subsets
def dfs(i):
if i==n: out.append(path[:]); return
path.append(a[i]); dfs(i+1); path.pop()
dfs(i+1)Signals: arrangements · use each once
def dfs():
if len(path)==n: out.append(path[:]); return
for x in nums:
if x in used: continue
used.add(x); path.append(x)
dfs()
path.pop(); used.remove(x)Signals: target sum · sorted choices
for i in range(start,n):
if i>start and a[i]==a[i-1]: continue
if a[i] > remain: break
path.append(a[i]); dfs(i+1, remain-a[i]); path.pop()The search tree is the algorithm. Prune branches as soon as they can no longer produce a valid answer; copy the path when saving it.
Brute-force instinct
Generating all binary strings and filtering wastes most branches. Build only prefixes where close<=open<=n; every explored prefix can still lead to a valid answer.
Pattern-level solution
The optimized approach follows the invariant taught above and removes repeated work or impossible candidates.
How to say it in an interview: "I encode validity in the recursion state, so I never recurse into a prefix with too many closing parentheses or too many opens."
| Operation / pattern | Time | Space / note |
|---|---|---|
| subsets | O(n·2^n) | O(n) path + output |
| permutations | O(n·n!) | O(n) path/used + output |
| N-Queens | O(n!) worst-case style | O(n) |
| word search | O(mn·4^L) | O(L) stack |
| parentheses | O(Catalan(n)·n) | O(n) path |
In Bangladesh-style timed assessments and live coding, recognise the pattern quickly, state the target complexity before coding, and test boundary cases. Python is typically acceptable because hiring is usually language-agnostic. Practise explaining the solution aloud and record yourself periodically.
| # | Problem | Level | Key idea | |
|---|---|---|---|---|
| 78 | Subsets core | Medium | Include/exclude or for-loop backtracking | |
| 46 | Permutations core | Medium | Used set/array + path | |
| 77 | Combinations core | Medium | start index prevents reordered duplicates | |
| 39 | Combination Sum core | Medium | Reuse current candidate; prune by remaining target | |
| 90 | Subsets II core | Medium | Sort + skip duplicate siblings | |
| 22 | Generate Parentheses core | Medium | Maintain open/close validity | |
| 79 | Word Search core | Medium | DFS grid with temporary visited mark | |
| 51 | N-Queens core | Hard | Columns and two diagonal sets | |
| 40 | Combination Sum II | Medium | Each candidate once + duplicate skipping | |
| 131 | Palindrome Partitioning | Medium | Choose next palindromic segment | |
| 17 | Letter Combinations of a Phone Number | Medium | Cartesian-product recursion | |
| 37 | Sudoku Solver | Hard | Constraint sets + backtracking |
Sorting is arranging books on a shelf by a chosen label. Once ordered, many later questions become much easier: duplicates cluster, intervals line up and binary search becomes possible.
Use these idioms as a starting point, then adapt them to the problem's exact constraints and return format.
# Python: stable multi-key sort
records.sort(key=lambda r: (r.department, -r.score, r.name))
# Lomuto partition skeleton
pivot = a[hi]
i = lo
for j in range(lo, hi):
if a[j] <= pivot:
a[i], a[j] = a[j], a[i]
i += 1
a[i], a[hi] = a[hi], a[i]
Signals: stable · divide and merge
def sort(a):
if len(a)<=1: return a
m=len(a)//2
return merge(sort(a[:m]), sort(a[m:]))Signals: pivot · in-place partition
pivot=a[hi]; i=lo
for j in range(lo,hi):
if a[j] <= pivot:
a[i],a[j]=a[j],a[i]; i+=1
a[i],a[hi]=a[hi],a[i]Signals: kth element · partial sort
while lo <= hi:
p=partition(a,lo,hi)
if p==k:return a[p]
if p<k:lo=p+1
else:hi=p-1Stable sorting preserves the relative order of equal keys. Python sorted/list.sort use Timsort; custom algorithms still matter for interview reasoning and partition patterns.
Brute-force instinct
Sorting all values costs O(n log n). Quickselect converts kth-largest to an index and repeatedly partitions only the relevant side, averaging O(n) time.
Pattern-level solution
The optimized approach follows the invariant taught above and removes repeated work or impossible candidates.
How to say it in an interview: "I do not need total order; partitioning tells me which side contains the target rank, so I discard the other side."
| Operation / pattern | Time | Space / note |
|---|---|---|
| Python sort | O(n log n) | O(n) worst auxiliary implementation detail |
| insertion sort | O(n²) | O(1) |
| merge sort | O(n log n) | O(n) |
| quicksort | avg O(n log n), worst O(n²) | O(log n) avg stack |
| quickselect | avg O(n), worst O(n²) | O(1) iterative |
In Bangladesh-style timed assessments and live coding, recognise the pattern quickly, state the target complexity before coding, and test boundary cases. Python is typically acceptable because hiring is usually language-agnostic. Practise explaining the solution aloud and record yourself periodically.
| # | Problem | Level | Key idea | |
|---|---|---|---|---|
| 912 | Sort an Array core | Medium | Implement merge sort or randomized quicksort | |
| 75 | Sort Colors core | Medium | Dutch national flag three-way partition | |
| 215 | Kth Largest Element in an Array core | Medium | Heap or quickselect | |
| 148 | Sort List core | Medium | Merge sort fits linked lists | |
| 56 | Merge Intervals core | Medium | Sort by start then merge overlaps | |
| 347 | Top K Frequent Elements core | Medium | Bucket sort by frequency or heap | |
| 451 | Sort Characters By Frequency | Medium | Frequency buckets/sort | |
| 164 | Maximum Gap | Medium | Bucket/pigeonhole idea after finding range | |
| 179 | Largest Number | Medium | Custom order comparing a+b vs b+a | |
| 973 | K Closest Points to Origin | Medium | Quickselect/heap by squared distance |
You are guessing a number and each answer says "too low" or "too high." That feedback discards half the possibilities every time.
Use these idioms as a starting point, then adapt them to the problem's exact constraints and return format.
# lower bound: first index with a[i] >= target
lo, hi = 0, len(a)
while lo < hi:
mid = (lo + hi) // 2
if a[mid] < target:
lo = mid + 1
else:
hi = mid
return lo
Signals: sorted · find target
lo,hi=0,n-1
while lo<=hi:
mid=(lo+hi)//2
if a[mid]==x:return mid
if a[mid]<x:lo=mid+1
else:hi=mid-1Signals: first >= x · first true
lo,hi=0,n
while lo<hi:
mid=(lo+hi)//2
if a[mid] < x: lo=mid+1
else: hi=mid
return loSignals: minimum feasible · maximum possible
lo,hi=min_answer,max_answer
while lo<hi:
mid=(lo+hi)//2
if feasible(mid): hi=mid
else: lo=mid+1
return loState the invariant first. For exact search use lo ≤ hi; for first-true/lower-bound searches, keep a half-open or closed invariant consistently.
Brute-force instinct
Trying every speed up to max(piles) is too slow. The predicate "can finish within h hours at speed k" changes from false to true as k increases, so binary-search the first feasible speed.
Pattern-level solution
The optimized approach follows the invariant taught above and removes repeated work or impossible candidates.
How to say it in an interview: "The answer space is monotonic: once a speed works, every larger speed works. I binary-search the minimum feasible speed."
| Operation / pattern | Time | Space / note |
|---|---|---|
| exact search | O(log n) | O(1) |
| lower/upper bound | O(log n) | O(1) |
| rotated search | O(log n) typical distinct values | O(1) |
| binary answer | O(log R · check) | depends on check |
| bisect insertion | O(log n) locate + O(n) list insert | O(1) |
In Bangladesh-style timed assessments and live coding, recognise the pattern quickly, state the target complexity before coding, and test boundary cases. Python is typically acceptable because hiring is usually language-agnostic. Practise explaining the solution aloud and record yourself periodically.
| # | Problem | Level | Key idea | |
|---|---|---|---|---|
| 704 | Binary Search core | Easy | Exact closed-interval search | |
| 35 | Search Insert Position core | Easy | Lower bound | |
| 34 | Find First and Last Position of Element in Sorted Array core | Medium | Two boundary searches | |
| 33 | Search in Rotated Sorted Array core | Medium | One half is sorted each iteration | |
| 153 | Find Minimum in Rotated Sorted Array core | Medium | Compare mid with right boundary | |
| 162 | Find Peak Element core | Medium | Follow uphill slope | |
| 74 | Search a 2D Matrix core | Medium | Flattened binary search | |
| 875 | Koko Eating Bananas core | Medium | Binary search minimum feasible speed | |
| 69 | Sqrt(x) | Easy | Search integer square root | |
| 1011 | Capacity To Ship Packages Within D Days | Medium | Binary search minimum capacity | |
| 410 | Split Array Largest Sum | Hard | Search minimal feasible maximum segment sum |
A tree is an organization chart. To answer a question about the CEO node, you often first ask each department subtree for a summary, then combine the summaries.
Use these idioms as a starting point, then adapt them to the problem's exact constraints and return format.
from collections import deque
def level_order(root):
if not root: return []
q = deque([root]); out = []
while q:
level = []
for _ in range(len(q)):
node = q.popleft(); level.append(node.val)
if node.left: q.append(node.left)
if node.right: q.append(node.right)
out.append(level)
return out
Signals: height · balance · diameter
def dfs(node):
if not node:return 0
L=dfs(node.left); R=dfs(node.right)
update_answer(L,R,node)
return 1+max(L,R)Signals: levels · shortest edges · right view
q=deque([root])
while q:
for _ in range(len(q)):
node=q.popleft()
if node.left:q.append(node.left)
if node.right:q.append(node.right)Signals: BST sorted order · avoid recursion
st=[]; cur=root
while cur or st:
while cur: st.append(cur); cur=cur.left
cur=st.pop(); visit(cur); cur=cur.rightDFS follows a branch; BFS processes a level. Most recursive tree solutions ask: what information should each subtree return to its parent?
Brute-force instinct
Checking path length through every pair would be expensive. One postorder DFS returns each subtree height; at each node left_height+right_height is the best diameter passing through that node, so update a global best.
Pattern-level solution
The optimized approach follows the invariant taught above and removes repeated work or impossible candidates.
How to say it in an interview: "Each child gives me height. I use those two heights both to update the best path through this node and to return one-sided height to the parent."
| Operation / pattern | Time | Space / note |
|---|---|---|
| DFS/BFS traversal | O(n) | O(h) DFS / O(width) BFS |
| max depth | O(n) | O(h) |
| diameter | O(n) | O(h) |
| build preorder+inorder | O(n) | O(n) index map |
| serialize/deserialize | O(n) | O(n) output/queue |
In Bangladesh-style timed assessments and live coding, recognise the pattern quickly, state the target complexity before coding, and test boundary cases. Python is typically acceptable because hiring is usually language-agnostic. Practise explaining the solution aloud and record yourself periodically.
| # | Problem | Level | Key idea | |
|---|---|---|---|---|
| 104 | Maximum Depth of Binary Tree core | Easy | 1 + max(left depth, right depth) | |
| 94 | Binary Tree Inorder Traversal core | Easy | Recursive or explicit stack | |
| 102 | Binary Tree Level Order Traversal core | Medium | Queue processed level by level | |
| 226 | Invert Binary Tree core | Easy | Swap children recursively/iteratively | |
| 543 | Diameter of Binary Tree core | Easy | Postorder heights + global best | |
| 236 | Lowest Common Ancestor of a Binary Tree core | Medium | Targets split across subtrees | |
| 112 | Path Sum core | Easy | Carry remaining target down | |
| 101 | Symmetric Tree core | Easy | Compare mirrored node pairs | |
| 100 | Same Tree core | Easy | Structure and values must match | |
| 199 | Binary Tree Right Side View core | Medium | Last node of each BFS level | |
| 103 | Binary Tree Zigzag Level Order Traversal | Medium | Alternate level direction | |
| 105 | Construct Binary Tree from Preorder and Inorder Traversal | Medium | Root from preorder + inorder boundaries | |
| 572 | Subtree of Another Tree | Easy | Match roots then same-tree check | |
| 124 | Binary Tree Maximum Path Sum | Hard | Return one-sided gain; update two-sided best | |
| 297 | Serialize and Deserialize Binary Tree | Hard | Encode values plus null markers |
A BST is a filing cabinet where every left drawer contains smaller keys and every right drawer contains larger keys. The comparison tells you which half of the tree can be ignored.
Use these idioms as a starting point, then adapt them to the problem's exact constraints and return format.
def valid(node, lo=float('-inf'), hi=float('inf')):
if not node: return True
if not (lo < node.val < hi): return False
return valid(node.left, lo, node.val) and valid(node.right, node.val, hi)
Signals: ordered comparisons
cur=root
while cur:
if x==cur.val: return cur
cur = cur.left if x<cur.val else cur.rightSignals: global BST property
def dfs(node,lo,hi):
if not node:return True
if not lo < node.val < hi:return False
return dfs(node.left,lo,node.val) and dfs(node.right,node.val,hi)Signals: kth smallest
st=[];cur=root
while True:
while cur:st.append(cur);cur=cur.left
cur=st.pop(); k-=1
if k==0:return cur.val
cur=cur.rightAll keys in the left subtree are smaller; all keys in the right are larger (under the chosen duplicate policy). Inorder traversal is sorted.
Brute-force instinct
A common wrong solution only checks node.left.val < node.val < node.right.val. That misses a deep descendant violating an ancestor boundary. Carry the full legal interval down the recursion.
Pattern-level solution
The optimized approach follows the invariant taught above and removes repeated work or impossible candidates.
How to say it in an interview: "Each recursive call receives the range every value in this subtree must satisfy, so ancestor constraints are preserved."
| Operation / pattern | Time | Space / note |
|---|---|---|
| search/insert/delete | O(h) | O(h) recursion stack possible |
| balanced h | O(log n) | — |
| degenerate h | O(n) | — |
| inorder traversal | O(n) | O(h) |
| kth smallest | O(h+k) early stop | O(h) |
In Bangladesh-style timed assessments and live coding, recognise the pattern quickly, state the target complexity before coding, and test boundary cases. Python is typically acceptable because hiring is usually language-agnostic. Practise explaining the solution aloud and record yourself periodically.
| # | Problem | Level | Key idea | |
|---|---|---|---|---|
| 700 | Search in a Binary Search Tree core | Easy | Follow comparisons down one branch | |
| 701 | Insert into a Binary Search Tree core | Medium | Recurse/iterate to empty child | |
| 98 | Validate Binary Search Tree core | Medium | Carry lower/upper bounds | |
| 230 | Kth Smallest Element in a BST core | Medium | Inorder traversal with early stop | |
| 450 | Delete Node in a BST | Medium | Leaf/one-child/two-child cases | |
| 235 | Lowest Common Ancestor of a Binary Search Tree | Medium | Both left, both right, or split | |
| 108 | Convert Sorted Array to Binary Search Tree | Easy | Middle element creates balanced root |
A heap is a tournament bracket where the winner is always at the top. You do not know the complete ranking, but you can quickly remove the current winner and repair the bracket.
Use these idioms as a starting point, then adapt them to the problem's exact constraints and return format.
import heapq
# kth largest: min-heap of k largest seen
heap = []
for x in nums:
heapq.heappush(heap, x)
if len(heap) > k:
heapq.heappop(heap)
return heap[0]
Signals: kth largest · k closest
h=[]
for x in items:
heapq.heappush(h, score(x))
if len(h)>k: heapq.heappop(h)Signals: merge sorted sources
h=[]
for source in sources:
push_first(source)
while h:
value, sid, pos = heapq.heappop(h)
output(value); push_next(sid,pos)Signals: stream median
# low = negatives (max-heap), high = min-heap
# rebalance so len(low) >= len(high) and diff <= 1heapq is a min-heap. Push/pop are O(log n); heapify is O(n). Negate priorities for max-heap behavior and add a tie-breaker when tuple payloads are not comparable.
Brute-force instinct
Sorting costs O(n log n). A min-heap of size k stores only the k largest values seen; its root is the kth largest. Each update costs log k, so O(n log k).
Pattern-level solution
The optimized approach follows the invariant taught above and removes repeated work or impossible candidates.
How to say it in an interview: "I only need the top k, not a total ordering, so I discard smaller elements as soon as the heap exceeds size k."
| Operation / pattern | Time | Space / note |
|---|---|---|
| peek min | O(1) | O(n) |
| push/pop | O(log n) | O(n) |
| heapify | O(n) | O(1) in-place list |
| top-k | O(n log k) | O(k) |
| merge k lists | O(N log k) | O(k) |
| median update | O(log n) | O(n) |
In Bangladesh-style timed assessments and live coding, recognise the pattern quickly, state the target complexity before coding, and test boundary cases. Python is typically acceptable because hiring is usually language-agnostic. Practise explaining the solution aloud and record yourself periodically.
| # | Problem | Level | Key idea | |
|---|---|---|---|---|
| 215 | Kth Largest Element in an Array core | Medium | Size-k min-heap or quickselect | |
| 703 | Kth Largest Element in a Stream core | Easy | Maintain size-k min-heap | |
| 973 | K Closest Points to Origin core | Medium | Heap/quickselect by squared distance | |
| 1046 | Last Stone Weight core | Easy | Max-heap via negative weights | |
| 295 | Find Median from Data Stream core | Hard | Max-heap lower half + min-heap upper half | |
| 23 | Merge k Sorted Lists | Hard | Heap one current node per list | |
| 621 | Task Scheduler | Medium | Frequencies + heap/greedy scheduling |
A graph is a city map: intersections are vertices and roads are edges. BFS explores by distance rings; DFS follows one road as far as possible before backtracking.
Use these idioms as a starting point, then adapt them to the problem's exact constraints and return format.
from collections import deque, defaultdict
def bfs(graph, start):
q = deque([start]); seen = {start}
while q:
u = q.popleft()
for v in graph[u]:
if v not in seen:
seen.add(v)
q.append(v)
Signals: shortest unweighted · layers · components
for start in graph:
if start in seen: continue
q=deque([start]); seen.add(start)
while q:
u=q.popleft()
for v in graph[u]:
if v not in seen: seen.add(v); q.append(v)Signals: prerequisites · DAG order
q=deque(v for v in nodes if indeg[v]==0)
order=[]
while q:
u=q.popleft(); order.append(u)
for v in g[u]:
indeg[v]-=1
if indeg[v]==0:q.append(v)Signals: dynamic connectivity · redundant edge
def find(x):
if parent[x]!=x: parent[x]=find(parent[x])
return parent[x]
def union(a,b):
ra,rb=find(a),find(b)
if ra==rb:return False
# attach smaller rank/size under largerMark BFS nodes when you enqueue them, not when you dequeue, or duplicates can flood the queue. Topological sort applies only to DAG ordering; union-find answers connectivity incrementally.
Brute-force instinct
Trying every ordering is factorial. Model each prerequisite as a directed edge. Kahn’s algorithm repeatedly removes zero-indegree courses; if every course is removed, an ordering exists, otherwise a directed cycle blocks completion.
Pattern-level solution
The optimized approach follows the invariant taught above and removes repeated work or impossible candidates.
How to say it in an interview: "This is cycle detection in a directed dependency graph. I use indegrees; processing all V nodes proves the graph is acyclic."
| Operation / pattern | Time | Space / note |
|---|---|---|
| BFS/DFS | O(V+E) | O(V) |
| grid traversal | O(rows·cols) | O(rows·cols) worst queue/seen |
| topological sort | O(V+E) | O(V) |
| union-find operations | amortized α(n) | O(V) |
| clone graph | O(V+E) | O(V) map |
In Bangladesh-style timed assessments and live coding, recognise the pattern quickly, state the target complexity before coding, and test boundary cases. Python is typically acceptable because hiring is usually language-agnostic. Practise explaining the solution aloud and record yourself periodically.
| # | Problem | Level | Key idea | |
|---|---|---|---|---|
| 200 | Number of Islands core | Medium | DFS/BFS each unseen land component | |
| 733 | Flood Fill core | Easy | Traverse same-color component | |
| 695 | Max Area of Island core | Medium | Component DFS returns/counts area | |
| 994 | Rotting Oranges core | Medium | Multi-source BFS by minutes/layers | |
| 207 | Course Schedule core | Medium | Directed cycle via Kahn or DFS colors | |
| 210 | Course Schedule II core | Medium | Return topological ordering | |
| 547 | Number of Provinces core | Medium | Components in adjacency matrix | |
| 684 | Redundant Connection core | Medium | Union-Find finds first edge closing cycle | |
| 785 | Is Graph Bipartite? core | Medium | Two-color every component | |
| 133 | Clone Graph core | Medium | Old→clone map + traversal | |
| 841 | Keys and Rooms core | Medium | Reachability DFS/BFS | |
| 130 | Surrounded Regions core | Medium | Mark border-connected O cells first | |
| 417 | Pacific Atlantic Water Flow core | Medium | Reverse flow from both oceans | |
| 1091 | Shortest Path in Binary Matrix | Medium | BFS in 8 directions | |
| 802 | Find Eventual Safe States | Medium | DFS state or reverse topological process | |
| 127 | Word Ladder | Hard | BFS word transformations |
Different road maps need different planners. If every road is nonnegative, Dijkstra works; negative tolls require a method that allows later corrections.
Use these idioms as a starting point, then adapt them to the problem's exact constraints and return format.
import heapq
def dijkstra(g, src):
dist = {src: 0}
pq = [(0, src)]
while pq:
d, u = heapq.heappop(pq)
if d != dist[u]: continue
for v, w in g[u]:
nd = d + w
if nd < dist.get(v, float('inf')):
dist[v] = nd
heapq.heappush(pq, (nd, v))
return dist
Signals: non-negative weights · shortest path
pq=[(0,src)]; dist={src:0}
while pq:
d,u=heappop(pq)
if d!=dist[u]:continue
for v,w in g[u]:
if d+w < dist.get(v,INF):
dist[v]=d+w; heappush(pq,(d+w,v))Signals: negative edges · bounded stops
dist=[INF]*n;dist[src]=0
for _ in range(n-1):
nxt=dist.copy()
for u,v,w in edges:
nxt[v]=min(nxt[v], dist[u]+w)
dist=nxtSignals: minimum total connection · undirected
for w,u,v in sorted(edges):
if union(u,v):
cost += w
used += 1Unweighted → BFS. Non-negative weights → Dijkstra. Negative edges → Bellman–Ford. All-pairs small graph → Floyd–Warshall. MST minimizes total connection cost, not source-to-target distance.
Brute-force instinct
Running a fresh search to every node repeats work. Dijkstra computes shortest distances from the source in one pass over the graph frontier; the answer is the maximum finalized distance if all nodes are reachable.
Pattern-level solution
The optimized approach follows the invariant taught above and removes repeated work or impossible candidates.
How to say it in an interview: "All weights are nonnegative, so once the smallest heap distance is finalized, no later path can improve it."
| Operation / pattern | Time | Space / note |
|---|---|---|
| Dijkstra | O((V+E) log V) | O(V+E) |
| Bellman–Ford | O(VE) | O(V) |
| Floyd–Warshall | O(V³) | O(V²) |
| 0-1 BFS | O(V+E) | O(V) |
| Kruskal | O(E log E) | O(V) |
| Prim | O(E log V) | O(V+E) |
In Bangladesh-style timed assessments and live coding, recognise the pattern quickly, state the target complexity before coding, and test boundary cases. Python is typically acceptable because hiring is usually language-agnostic. Practise explaining the solution aloud and record yourself periodically.
| # | Problem | Level | Key idea | |
|---|---|---|---|---|
| 743 | Network Delay Time core | Medium | Dijkstra from source; answer max distance | |
| 787 | Cheapest Flights Within K Stops core | Medium | K+1 edge-limited relaxations or stateful shortest path | |
| 1584 | Min Cost to Connect All Points core | Medium | MST with Prim/Kruskal | |
| 1631 | Path With Minimum Effort core | Medium | Dijkstra where path cost is max edge | |
| 1514 | Path with Maximum Probability | Medium | Max-priority Dijkstra on probability product | |
| 1334 | Find the City With the Smallest Number of Neighbors at a Threshold Distance | Medium | All-pairs shortest paths or repeated Dijkstra |
A greedy cashier chooses the best-looking move now and never revisits it. That is only correct when you can prove an optimal solution can be rearranged to start with the same choice.
Use these idioms as a starting point, then adapt them to the problem's exact constraints and return format.
# maximum number of non-overlapping intervals
intervals.sort(key=lambda x: x[1])
end = float('-inf')
kept = 0
for s, e in intervals:
if s >= end:
kept += 1
end = e
Signals: interval scheduling · removals
intervals.sort(key=lambda x:x[1])
end=-INF
for s,e in intervals:
if s>=end:
keep(); end=eSignals: jump game · coverage
far=0
for i,jump in enumerate(nums):
if i>far:return False
far=max(far,i+jump)
return TrueSignals: circular route · net gain
if sum(gas)<sum(cost):return -1
start=tank=0
for i in range(n):
tank += gas[i]-cost[i]
if tank<0:start=i+1;tank=0
return startA greedy rule needs a proof idea: exchange an optimal choice with the greedy choice without making the solution worse. When that argument fails, dynamic programming may be required.
Brute-force instinct
Checking all subsets is exponential. Sort by end time; keeping the interval that finishes earliest leaves at least as much room for every future interval as any later-finishing choice.
Pattern-level solution
The optimized approach follows the invariant taught above and removes repeated work or impossible candidates.
How to say it in an interview: "The exchange argument is that replacing a kept interval with an earlier-finishing compatible one cannot reduce future options."
| Operation / pattern | Time | Space / note |
|---|---|---|
| interval sort | O(n log n) | O(1) extra aside from sort |
| jump game | O(n) | O(1) |
| gas station | O(n) | O(1) |
| partition labels | O(n) | O(alphabet) |
| merge intervals | O(n log n) | O(n) output |
In Bangladesh-style timed assessments and live coding, recognise the pattern quickly, state the target complexity before coding, and test boundary cases. Python is typically acceptable because hiring is usually language-agnostic. Practise explaining the solution aloud and record yourself periodically.
| # | Problem | Level | Key idea | |
|---|---|---|---|---|
| 455 | Assign Cookies core | Easy | Sort both; give smallest sufficient cookie | |
| 55 | Jump Game core | Medium | Track farthest reachable index | |
| 45 | Jump Game II core | Medium | Greedy BFS-style range expansion | |
| 134 | Gas Station core | Medium | Total feasibility + reset failed starts | |
| 763 | Partition Labels core | Medium | Close when reaching farthest last occurrence | |
| 860 | Lemonade Change | Easy | Preserve flexible $5 bills; greedy change | |
| 56 | Merge Intervals | Medium | Sort by start and merge overlap | |
| 57 | Insert Interval | Medium | Add before/merge/after in ordered list | |
| 435 | Non-overlapping Intervals | Medium | Keep earliest finishing interval | |
| 452 | Minimum Number of Arrows to Burst Balloons | Medium | Shoot at earliest end among overlapping balloons |
DP is filling a workbook where each answer may reuse answers from earlier pages. The hard part is deciding what one cell means; after that, the recurrence and order follow.
Use these idioms as a starting point, then adapt them to the problem's exact constraints and return format.
# 0/1 subset-sum feasibility
can = [False] * (target + 1)
can[0] = True
for x in nums:
for s in range(target, x - 1, -1):
can[s] |= can[s - x]
return can[target]
Signals: recursive recurrence · sparse states
from functools import cache
@cache
def dp(state):
if base(state): return base_value
return best(dp(next_state) + cost for next_state in moves(state))Signals: stairs · robber
prev2,prev1=base0,base1
for i in range(2,n+1):
cur=transition(prev2,prev1)
prev2,prev1=prev1,curSignals: choose each item once
dp=[0]*(cap+1)
for w,val in items:
for c in range(cap,w-1,-1):
dp[c]=max(dp[c],dp[c-w]+val)Signals: LCS · edit distance
dp=[[0]*(n+1) for _ in range(m+1)]
for i in range(1,m+1):
for j in range(1,n+1):
dp[i][j]=transition(...)For every DP problem name the state, transition, base cases, computation order and final answer. Then ask whether old rows/columns can be discarded to optimize space.
Brute-force instinct
Naive recursion tries many repeated remaining amounts. Define dp[a] as the fewest coins to form amount a. Each state considers dp[a-coin]+1, so tabulation solves each amount once. Impossible states stay infinity.
Pattern-level solution
The optimized approach follows the invariant taught above and removes repeated work or impossible candidates.
How to say it in an interview: "My state is the best answer for one remaining/constructed amount. Because transitions only depend on smaller amounts, I can fill amounts from 0 upward."
| Operation / pattern | Time | Space / note |
|---|---|---|
| 1D DP | often O(n) | O(n) → O(1) if local deps |
| knapsack/subset | O(n·target) | O(target) |
| LIS classic | O(n²) | O(n) |
| LIS optimized | O(n log n) | O(n) |
| grid DP | O(mn) | O(mn) or O(n) |
| LCS/edit distance | O(mn) | O(mn) or row-optimized |
| interval DP | often O(n³) | O(n²) |
In Bangladesh-style timed assessments and live coding, recognise the pattern quickly, state the target complexity before coding, and test boundary cases. Python is typically acceptable because hiring is usually language-agnostic. Practise explaining the solution aloud and record yourself periodically.
| # | Problem | Level | Key idea | |
|---|---|---|---|---|
| 70 | Climbing Stairs core | Easy | dp[i]=dp[i-1]+dp[i-2] | |
| 198 | House Robber core | Medium | max(skip, take + dp[i-2]) | |
| 213 | House Robber II core | Medium | Max of two linear ranges excluding one endpoint | |
| 91 | Decode Ways core | Medium | One-digit and valid two-digit transitions | |
| 322 | Coin Change core | Medium | Minimum over dp[amount-coin]+1 | |
| 518 | Coin Change II core | Medium | Count combinations; coin outer loop | |
| 416 | Partition Equal Subset Sum core | Medium | 0/1 subset sum to total/2 | |
| 300 | Longest Increasing Subsequence core | Medium | O(n²) DP or tails + bisect_left | |
| 62 | Unique Paths core | Medium | Top + left grid recurrence | |
| 64 | Minimum Path Sum core | Medium | Cell cost + min(top,left) | |
| 1143 | Longest Common Subsequence core | Medium | Prefix pair DP | |
| 72 | Edit Distance core | Medium | Insert/delete/replace recurrence | |
| 139 | Word Break core | Medium | Reachable prefix DP | |
| 647 | Palindromic Substrings core | Medium | Expand centers or interval DP | |
| 309 | Best Time to Buy and Sell Stock with Cooldown core | Medium | State machine DP | |
| 494 | Target Sum core | Medium | Signed-sum memo or subset transform | |
| 221 | Maximal Square core | Medium | 1 + min(top,left,diag) for 1-cells | |
| 120 | Triangle | Medium | Bottom-up min path | |
| 5 | Longest Palindromic Substring | Medium | Expand around every center | |
| 312 | Burst Balloons | Hard | Interval DP choose last balloon | |
| 115 | Distinct Subsequences | Hard | Count prefix matches |
A trie is a dictionary where words sharing the same beginning literally share the same hallway until their letters diverge.
Use these idioms as a starting point, then adapt them to the problem's exact constraints and return format.
class TrieNode:
def __init__(self):
self.children = {}
self.is_end = False
class Trie:
def __init__(self): self.root = TrieNode()
def insert(self, word):
node = self.root
for ch in word:
node = node.children.setdefault(ch, TrieNode())
node.is_end = True
Signals: dictionary words · prefixes
node=root
for ch in word:
if ch not in node.children:return False
node=node.children[ch]
return node.is_endSignals: suggestions · prefix query
node=walk(prefix)
# DFS descendants, append when is_end, stop after kSignals: pattern with dot wildcard
def dfs(i,node):
if i==len(word): return node.is_end
if word[i]=='.': return any(dfs(i+1,c) for c in node.children.values())
return word[i] in node.children and dfs(i+1,node.children[word[i]])Each edge is a character; a terminal flag distinguishes a complete word from a mere prefix. Search time is O(length of word), independent of how many words share the prefix.
Brute-force instinct
A normal trie search follows one edge per letter. A dot wildcard cannot choose a single edge, so recurse into every child only at wildcard positions while normal characters remain O(1)-branch.
Pattern-level solution
The optimized approach follows the invariant taught above and removes repeated work or impossible candidates.
How to say it in an interview: "The trie prunes by fixed prefixes; wildcard positions branch over existing children rather than the entire alphabet."
| Operation / pattern | Time | Space / note |
|---|---|---|
| insert | O(L) | O(L) new nodes worst |
| exact search | O(L) | O(1) extra iterative |
| prefix search | O(L) | O(1) |
| autocomplete | O(L + visited/output) | depends on subtree |
| wildcard | worst exponential in wildcards | recursion depth O(L) |
In Bangladesh-style timed assessments and live coding, recognise the pattern quickly, state the target complexity before coding, and test boundary cases. Python is typically acceptable because hiring is usually language-agnostic. Practise explaining the solution aloud and record yourself periodically.
| # | Problem | Level | Key idea | |
|---|---|---|---|---|
| 208 | Implement Trie (Prefix Tree) core | Medium | children dict + terminal flag | |
| 211 | Design Add and Search Words Data Structure core | Medium | DFS branches on dot wildcard | |
| 212 | Word Search II core | Hard | Board DFS pruned by trie prefixes | |
| 648 | Replace Words | Medium | Find shortest terminal prefix | |
| 1268 | Search Suggestions System | Medium | Trie or sorted range for each prefix |
An integer is a row of on/off switches. Bit operations let you test, flip, combine or remove switches without converting to strings.
Use these idioms as a starting point, then adapt them to the problem's exact constraints and return format.
# Kernighan popcount
count = 0
while x:
x &= x - 1
count += 1
# enumerate subsets by mask
for mask in range(1 << n):
subset = [a[i] for i in range(n) if mask & (1 << i)]
Signals: single odd-occurring value
ans=0
for x in nums: ans ^= x
return ansSignals: popcount · bit iteration
count=0
while x:
x &= x-1
count += 1Signals: all primes up to n
prime=[True]*(n+1)
prime[:2]=[False,False]
for p in range(2,int(n**0.5)+1):
if prime[p]:
for m in range(p*p,n+1,p): prime[m]=Falsex & (x-1) removes the lowest set bit. XOR cancels equal values. Remember Python integers are unbounded, so fixed-width bit problems may need a 32-bit mask.
Brute-force instinct
A set/count map works in O(n) time and O(n) space. XOR uses the algebraic cancellation property to remove paired values in O(n) time and O(1) extra space.
Pattern-level solution
The optimized approach follows the invariant taught above and removes repeated work or impossible candidates.
How to say it in an interview: "Because XOR is associative/commutative and x^x=0, all paired numbers cancel and only the unpaired value remains."
| Operation / pattern | Time | Space / note |
|---|---|---|
| bit test/set | O(1) | O(1) |
| Kernighan count | O(popcount) | O(1) |
| bitmask subsets | O(n·2^n) | O(n) current subset |
| gcd | O(log min(a,b)) | O(1) |
| sieve | O(n log log n) | O(n) |
| pow(a,b,mod) | O(log b) | O(1) |
In Bangladesh-style timed assessments and live coding, recognise the pattern quickly, state the target complexity before coding, and test boundary cases. Python is typically acceptable because hiring is usually language-agnostic. Practise explaining the solution aloud and record yourself periodically.
| # | Problem | Level | Key idea | |
|---|---|---|---|---|
| 136 | Single Number core | Easy | XOR all values | |
| 191 | Number of 1 Bits core | Easy | x &= x-1 repeatedly | |
| 338 | Counting Bits core | Easy | dp[i]=dp[i>>1]+(i&1) | |
| 190 | Reverse Bits | Easy | Shift result and consume 32 bits | |
| 268 | Missing Number | Easy | XOR indices and values or arithmetic sum | |
| 371 | Sum of Two Integers | Medium | XOR sum + AND carry under 32-bit mask | |
| 202 | Happy Number | Easy | Digit-square transform + cycle detection | |
| 204 | Count Primes | Medium | Sieve of Eratosthenes | |
| 7 | Reverse Integer | Medium | Rebuild digits with range check |
A mock interview is a flight simulator: the point is not to prove you know everything, but to practise the exact sequence of clarifying, reasoning, coding, testing and explaining under time pressure.
Use these idioms as a starting point, then adapt them to the problem's exact constraints and return format.
def run_tests(fn, cases):
for args, expected in cases:
got = fn(*args)
status = "PASS" if got == expected else "FAIL"
print(status, args, "=>", got, "expected", expected)
# Deliberate boundary bug to catch in a mock:
def lower_bound_bug(a, x):
lo, hi = 0, len(a) - 1 # wrong for insertion at len(a)
while lo < hi:
mid = (lo + hi) // 2
if a[mid] < x: lo = mid + 1
else: hi = mid
return lo
Signals: live coding · think aloud
clarify()
brute_force()
state_complexity()
optimize_by_pattern()
code_cleanly()
dry_run()
test_edges()
state_final_complexity()Signals: repeat mistakes deliberately
log = {
"signal_missed": "contiguous + longest",
"bug": "used if instead of while when shrinking",
"fix": "state window invariant first"
}Signals: maximize correct submissions
# 1 scan all
# 2 solve easiest complete tasks
# 3 optimize by constraints
# 4 reserve test/review timeNarrate decisions, not every keystroke. State assumptions, test edge cases, and leave enough time to verify complexity and one manual example.
Brute-force instinct
Do not jump directly to memorized code. For each problem, state the brute force and why it fails the constraints, identify the structural signal, state an invariant, then implement. This makes the solution adaptable if the interviewer changes the requirement.
Pattern-level solution
The optimized approach follows the invariant taught above and removes repeated work or impossible candidates.
How to say it in an interview: "I’ll first confirm constraints, give the simplest correct approach, then improve the bottleneck. I’ll state the invariant before coding so we can verify correctness as we go."
| Operation / pattern | Time | Space / note |
|---|---|---|
| clarification | ~5 min | requirements/examples |
| brute + optimize | ~10 min | complexity/pattern |
| implementation | ~20 min | clean code |
| tests | ~7 min | edge cases/dry run |
| complexity/follow-up | ~3 min | trade-offs |
In Bangladesh-style timed assessments and live coding, recognise the pattern quickly, state the target complexity before coding, and test boundary cases. Python is typically acceptable because hiring is usually language-agnostic. Practise explaining the solution aloud and record yourself periodically.
| # | Problem | Level | Key idea | |
|---|---|---|---|---|
| 217 | Contains Duplicate core | Easy | Mock A easy: seen set | |
| 3 | Longest Substring Without Repeating Characters core | Medium | Mock A medium: variable window | |
| 56 | Merge Intervals core | Medium | Mock A medium: sort and merge | |
| 704 | Binary Search core | Easy | Mock B easy: exact invariant | |
| 198 | House Robber core | Medium | Mock B medium: 1D DP | |
| 207 | Course Schedule core | Medium | Mock B medium: topological cycle check | |
| 121 | Best Time to Buy and Sell Stock core | Easy | Mock C easy: running minimum | |
| 200 | Number of Islands | Medium | Mock C medium: grid components | |
| 215 | Kth Largest Element in an Array | Medium | Mock C medium: heap or quickselect |
Longest/shortest substring or subarray with a changing condition.
Sliding Window →Opposite ends or read/write indices.
Two Pointers →Complement, frequency, group or seen membership.
Hashing →Unresolved candidates or LIFO matching.
Stacks →Target search or minimum feasible answer.
Binary Search →Subtree returns, levels, ancestor relations.
Trees →Traversal, topological order, union-find.
Graphs →Shortest path, all-pairs or minimum spanning tree.
Shortest Paths →State, transition and cached/tabulated answers.
Dynamic Programming →Decision tree with choose/explore/unchoose.
Backtracking →Top-k, scheduling, merge-k, median stream.
Heaps →Shared prefix paths, autocomplete, wildcard search.
Tries →| Signal | Likely pattern |
|---|---|
| sorted + pair/closest | two pointers |
| contiguous longest/shortest | sliding window |
| many range sums | prefix sums |
| count/lookup/complement | hashing |
| next greater / nearest smaller | monotonic stack |
| minimum feasible / maximum possible | binary search on answer |
| unweighted shortest path | BFS |
| dependency ordering | topological sort |
| dynamic connectivity | union-find |
| nonnegative weighted shortest path | Dijkstra |
| all combinations/arrangements | backtracking |
| overlapping states / optimize choices | DP |
| Typical n | Usually reasonable |
|---|---|
| ≤ 20 | 2^n / backtracking may fit |
| ≤ 100 | n³ sometimes |
| ≤ 1,000 | n² often |
| ≤ 100,000 | n log n or n |
| ≥ 1,000,000 | near-linear / streaming |
| Operation | Cost |
|---|---|
| list index | O(1) |
| list append | amortized O(1) |
| list insert/pop at front | O(n) |
| dict/set membership | average O(1) |
| deque append/popleft | O(1) |
| heap push/pop | O(log n) |
| sort | O(n log n) |
| slice of k items | O(k) |
l,r=0,len(a)-1
while l<r:
if ok(a[l],a[r]): ...
elif need_bigger: l+=1
else: r-=1l=0
for r,x in enumerate(a):
add(x)
while invalid(): remove(a[l]); l+=1
best=max(best,r-l+1)lo,hi=0,len(a)
while lo<hi:
mid=(lo+hi)//2
if a[mid] < x: lo=mid+1
else: hi=midq=deque([s]); seen={s}
while q:
u=q.popleft()
for v in g[u]:
if v not in seen: seen.add(v); q.append(v)def dfs(r,c):
if out_or_blocked(r,c): return
mark(r,c)
for dr,dc in DIRS: dfs(r+dr,c+dc)q=deque(v for v in nodes if indeg[v]==0)
while q:
u=q.popleft(); order.append(u)
for v in g[u]:
indeg[v]-=1
if indeg[v]==0:q.append(v)def find(x):
if parent[x]!=x: parent[x]=find(parent[x])
return parent[x]pq=[(0,s)];dist={s:0}
while pq:
d,u=heappop(pq)
if d!=dist[u]:continue
for v,w in g[u]:
if d+w < dist.get(v,INF): ...def dfs(start):
if done(): out.append(path[:]); return
for i in range(start,n):
choose(i); dfs(i+1); unchoose(i)@cache
def dp(state):
if base(state): return base_value
return best(dp(nxt)+cost for nxt in moves(state))st=[]
for i,x in enumerate(a):
while st and a[st[-1]] < x:
j=st.pop(); ans[j]=i
st.append(i)h=[]
for x in items:
heappush(h,x)
if len(h)>k:heappop(h)node=root
for ch in word:
node=node.children.setdefault(ch,Node())
node.is_end=Truefreq={0:1};p=ans=0
for x in a:
p+=x; ans+=freq.get(p-k,0)
freq[p]=freq.get(p,0)+1