2BIT Pathshala
deep revision · properties · templates · algorithms · diagrams
Notes Patterns

DSA Cheat Sheet: Deep Notes

Every block: when to use → key properties → sub-topics → Python template → diagram. Skim before an interview; jump via the tabs above.

⏱️ Complexity & Data Structures

Input size → feasible complexity

nApproach
≤ 12O(n!) permutations
≤ 20O(2ⁿ) subsets / bitmask
≤ 100O(n³) Floyd-Warshall
≤ 5·10³O(n²) DP
≤ 10⁶O(n log n) sort / heap
≥ 10⁷O(n) / O(log n)

Operation costs

DSAccessIns/DelSearch
Dynamic arrayO(1)O(n) / O(1)*O(n)
Hash map/setn/aO(1)O(1)
Balanced BSTO(log n)O(log n)O(log n)
HeapO(1) topO(log n)O(n)
Trien/aO(L)O(L)
*append amortized O(1). Recursion adds O(depth) stack space. Set sys.setrecursionlimit(10**6) for deep trees.

🔢 Arrays & Two Pointers

When: sorted data, pair/triplet sums, in-place partition, palindrome, dedupe, container problems.
PROPERTY Sorting unlocks two-pointers & binary search (turns O(n²)→O(n log n)). In-place tricks use the array itself as storage (index-as-hash).

Sub-topics

  • Opposite ends: 2-sum sorted, 3-sum (fix i, 2-ptr rest), container water, trapping rain.
  • Same direction: remove dupes, move zeroes (slow=write).
  • Dutch flag: 3-way partition (sort colors) with low/mid/high.
  • Boyer–Moore voting: majority element (n/2) in O(1) space.
  • Next Permutation: find first a[i]<a[i+1] from right, swap w/ next larger, reverse suffix.
  • Kadane: max subarray, reset running sum at 0.
def three_sum(a):
    a.sort(); res=[]
    for i in range(len(a)-2):
        if i and a[i]==a[i-1]: continue      # skip dup
        l, r = i+1, len(a)-1
        while l < r:
            s = a[i]+a[l]+a[r]
            if s==0:
                res.append([a[i],a[l],a[r]])
                l+=1; r-=1
                while l
          
Kadane:  cur = max(x, cur+x); best = max(best, cur)
Dutch flag:  [0..low) 0s | [low..mid) 1s | mid..high ? | (high..] 2s

🪟 Sliding Window

When: longest/shortest/count of a contiguous subarray/substring meeting a constraint.
TRICK "Exactly K" = atMost(K) − atMost(K−1). Window max/min → monotonic deque.
  • Fixed size k: add a[r], remove a[r-k].
  • Variable: grow right; while invalid shrink left; record best.
  • Track state: running sum or a frequency map.
def longest_no_repeat(s):
    last, left, best = {}, 0, 0
    for r, ch in enumerate(s):
        if ch in last and last[ch] >= left:
            left = last[ch] + 1        # jump left
        last[ch] = r
        best = max(best, r - left + 1)
    return best
Monotonic deque (window max):
  keep indices, values DECREASING
  pop_back while a[back] <= a[r]
  pop_front if front <= r-k
  front = current window max

🔍 Binary Search

When: sorted search space, OR a monotonic predicate (feasible→infeasible). "min/max X such that check(X)".
PROPERTY bisect_left = first index ≥ x (lower bound); bisect_right = first index > x. On rotated arrays, one half is always sorted.

Sub-topics

  • On index: classic, first/last occurrence, rotated search, peak element.
  • On answer: Koko bananas, ship in D days, split array, allocate books, aggressive cows.
  • On floats: nth root, iterate ~100× or until precision.
  • 2D matrix: treat as flattened sorted array, or staircase from top-right.
def lower_bound(a, x):
    lo, hi = 0, len(a)
    while lo < hi:
        mid = (lo+hi)//2
        if a[mid] < x: lo = mid+1
        else:          hi = mid
    return lo                     # first idx with a[idx] >= x

def min_feasible(lo, hi, ok):
    while lo < hi:
        mid = (lo+hi)//2
        if ok(mid): hi = mid
        else:       lo = mid+1
    return lo

➕ Prefix Sum, Difference & Hashing

Prefix / Difference

  • Prefix sum: range = pre[r]-pre[l-1].
  • #subarrays sum=k: store freq of prefix; ans+=seen[pre-k].
  • Prefix XOR: subarray XOR = pre[r]^pre[l-1].
  • 2D prefix: submatrix sum O(1).
  • Difference array: range update +v at l, −v at r+1; prefix to apply. Great for many range adds.
seen={0:1}; pre=ans=0
for x in nums:
    pre+=x
    ans+=seen.get(pre-k,0)
    seen[pre]=seen.get(pre,0)+1

Hashing

  • "seen / count / group / pair" → dict or set (O(1)).
  • Anagram key = sorted string OR 26-length count tuple.
  • Longest consecutive: only start counting at nums without n-1 present → O(n).
  • Index-as-hash: put value v at index v (cyclic sort) for 1..n problems.
Counter, defaultdict(list), set: memorize these.

📚 Stack, Monotonic Stack & Queue

When: matching, "next greater/smaller", histogram, spans, expression eval; queues for BFS/level order.
RULE Next Greater → decreasing stack. Next Smaller → increasing stack. Store indices to compute widths/spans.

Sub-topics

  • NGE/NSE, daily temperatures, stock span.
  • Largest rectangle in histogram (nearest smaller both sides).
  • Sum of subarray minimums (contribution technique).
  • Min-stack (store (val,curMin)); valid parentheses; RPN eval; basic calculator.
  • LRU = hashmap + doubly linked list; LFU adds freq buckets.
  • Deque = sliding window max/min.
def largest_rectangle(h):
    h.append(0); st=[]; best=0        # sentinel
    for i, x in enumerate(h):
        while st and h[st[-1]] >= x:
            height = h[st.pop()]
            width = i if not st else i-st[-1]-1
            best = max(best, height*width)
        st.append(i)
    return best
NGE: [2,1,3] -> decreasing stack of idx
     push 0; push1; 3 pops 1&0 -> ans[1]=3,ans[0]=3

🔗 Linked List

When: pointer surgery. Tools: dummy head, fast/slow, iterative reverse.
PROPERTY Floyd's cycle: slow+1/fast+2 meet inside a cycle; reset one ptr to head, move both +1 → meet at cycle start.

Sub-topics

  • Reverse (full / between / k-group).
  • Cycle detect + find start; find middle; palindrome.
  • Merge two / merge k (heap); add two numbers.
  • Remove Nth from end (two ptr gap = n).
  • Copy list with random pointer; reorder list.
def reverse(head):
    prev=None
    while head:
        head.next, prev, head = prev, head, head.next
    return prev

dummy = ListNode(0, head)           # simplifies edge cases
# remove nth from end: advance fast n+1 steps, then move both
reverse:  None<-1  2->3   (flip each next to prev)

🌳 Binary Tree

When: hierarchical data / divide-and-conquer. Solve children first, combine at node.
PROPERTIES Height of node = 1+max(childHeights). #nodes in complete tree ≈ 2^h. DFS uses stack/recursion; BFS uses a queue (level by level).

Sub-topics

  • Traversals: pre/in/post (rec + iterative), Morris (O(1) space, thread to predecessor).
  • Views: left/right (last per level), top/bottom (by horizontal distance).
  • Metrics: height, diameter (max L+R depth), balanced, count nodes.
  • Paths: root-to-leaf sum, max path sum (return one side, update global).
  • LCA: return node if it equals p/q or splits.
  • Construct from pre+in / in+post; serialize (preorder + null markers).
  • Distance-K (convert to graph via parent links, BFS).
def diameter(root):
    best = 0
    def depth(n):
        nonlocal best
        if not n: return 0
        L, R = depth(n.left), depth(n.right)
        best = max(best, L + R)     # path through n
        return 1 + max(L, R)
    depth(root); return best
        1
       / \
      2   3      Pre 1 2 4 5 3 | In 4 2 5 1 3
     / \         Post 4 5 2 3 1 | BFS 1|2 3|4 5
    4   5

🌲 Binary Search Tree (BST)

When: ordered data with fast search/insert/delete in O(height).
KEY PROPERTY Inorder traversal of a BST is sorted (ascending). So: print sorted = inorder; Kth smallest = Kth node in inorder; validate = inorder must be strictly increasing; Two-Sum in BST = inorder + two pointers.

Sub-topics & properties

  • Search/Insert: go left if target < node else right (O(h)).
  • Delete: replace with inorder successor (smallest in right subtree).
  • Validate: pass (low, high) bounds down, or check inorder increasing.
  • LCA in BST: walk down; the first node between p and q is the LCA.
  • Floor/Ceil: track candidate while walking down.
  • Kth smallest/largest: inorder (or reverse-inorder) with a counter.
  • Predecessor/Successor via inorder neighbors.
def kth_smallest(root, k):
    st, node = [], root
    while st or node:
        while node:                 # go left
            st.append(node); node = node.left
        node = st.pop(); k -= 1     # inorder visit
        if k == 0: return node.val
        node = node.right

def lca_bst(root, p, q):
    while root:
        if proot.val and q>root.val: root=root.right
        else: return root           # split point

⛰️ Heap / Priority Queue

When: "Top-K", "Kth", "median of stream", "merge k sorted", scheduling by priority.
PROPERTIES Array heap: parent(i)=(i-1)//2, children=2i+1,2i+2. heapify a list in O(n) (not n log n). Python heapq is a MIN-heap; push -x for max.
  • Kth largest: min-heap of size k → root = answer.
  • Top-K frequent: Counter + heap (or bucket sort).
  • Median of stream: two heaps, keep sizes balanced.
  • Merge k lists/arrays: heap of (val, listIdx, elemIdx).
  • Greedy + heap: task scheduler, IPO, connect ropes.
Two-heap median:
  maxHeap(low half)   minHeap(high half)
     tops give median; rebalance if sizes differ by >1

heapify([...])  ->  O(n)   (sift-down from last parent up)

🌿 Recursion & Backtracking

When: enumerate all subsets/permutations/combinations, grid paths, constraint puzzles.
TEMPLATE choose → explore → un-choose. Combos use a start index (order doesn't matter); permutations use used[]. Sort input to skip duplicates.
  • Subsets 2ⁿ; permutations n!; combination sum (reuse vs not).
  • N-Queens/Sudoku: track cols & diagonals in sets; prune.
  • Word search: DFS + mark visited, unmark on return.
  • Palindrome partitioning: try every prefix that is a palindrome.
def subsets(nums):
    res=[]
    def bt(start, path):
        res.append(path[:])
        for i in range(start, len(nums)):
            path.append(nums[i])
            bt(i+1, path)          # i+1: no reuse
            path.pop()
    bt(0, []); return res
decision tree (include/exclude):
            []
        /        \
      [1]         []
     /   \       /  \
  [1,2] [1]   [2]   []

🕸️ Graphs

When: connectivity, paths, ordering, cycles. Model as adjacency list. Always track visited.
CHOOSE Unweighted shortest path → BFS. 0/1 weights → 0-1 BFS (deque). Non-negative → Dijkstra. Negative edges → Bellman-Ford. All-pairs → Floyd-Warshall. DAG order → Topological sort. Directed & undirected: all work on both (an undirected edge = two directed edges u→w, w→u); but negative weights only apply to directed graphs, since a negative undirected edge is itself a negative cycle.

Traversal / Topo / Cycle

from collections import deque, defaultdict
def topo_kahn(n, edges):
    adj=defaultdict(list); indeg=[0]*n
    for u,v in edges: adj[u].append(v); indeg[v]+=1
    q=deque([i for i in range(n) if indeg[i]==0]); order=[]
    while q:
        u=q.popleft(); order.append(u)
        for v in adj[u]:
            indeg[v]-=1
            if indeg[v]==0: q.append(v)
    return order if len(order)==n else []   # [] => cycle
  • Cycle (directed): DFS 3-color (white/gray/black) or Kahn fails.
  • Cycle (undirected): DSU union, or DFS with parent.
  • Bipartite: 2-color via BFS; conflict → not bipartite.

Shortest path / MST

import heapq
def dijkstra(src, adj, n):
    dist=[float('inf')]*n; dist[src]=0; pq=[(0,src)]
    while pq:
        d,u=heapq.heappop(pq)
        if d>dist[u]: continue
        for v,w in adj[u]:
            if d+w
          
  • Bellman-Ford: relax all edges V−1 times; extra relax → negative cycle.
  • Floyd-Warshall: dp[i][j]=min(dp[i][j], dp[i][k]+dp[k][j]).
  • MST: Kruskal (sort edges + DSU) or Prim (heap).
Grid = graph: cell neighbors = 4/8 directions. "Spread/rot/nearest" → multi-source BFS (push all sources first).

🔗 Union-Find (DSU)

When: dynamic connectivity, count components, cycle in undirected graph, Kruskal MST.
PROPERTY With path compression + union by rank/size, operations are ~O(α(n)) ≈ O(1) amortized.
parent=list(range(n)); rank=[0]*n
def find(x):
    while parent[x]!=x:
        parent[x]=parent[parent[x]]          # path compression
        x=parent[x]
    return x
def union(a,b):
    ra,rb=find(a),find(b)
    if ra==rb: return False                  # already connected (cycle!)
    if rank[ra]
    

🔤 Trie (Prefix Tree)

When: many prefix/word lookups, autocomplete, dictionary matching, and max-XOR (bitwise trie).
PROPERTY Insert/search/prefix are O(word length), independent of #words. Bitwise trie stores numbers MSB→LSB; to maximize XOR, greedily take the opposite bit.
class Trie:
    def __init__(self): self.root={}
    def insert(self, w):
        node=self.root
        for c in w: node=node.setdefault(c, {})
        node['$']=True
    def starts_with(self, pre):
        node=self.root
        for c in pre:
            if c not in node: return False
            node=node[c]
        return True

🧮 Dynamic Programming

When: overlapping subproblems + optimal substructure. "count ways / min-max / can we reach".
METHOD 1) define state (what varies) 2) transition 3) base case 4) order (memo top-down first, then tabulate). If greedy is hard to justify → it's usually DP.

Pattern → state

PatternState / transition
1D (rob/stairs)dp[i] from dp[i-1], dp[i-2]
0/1 Knapsackdp[i][cap] take/skip
Unboundedreuse item (coin change)
LCS / Editdp[i][j] over 2 strings
LISdp[i]=1+max(dp[j]); n log n patience
Griddp[i][j]=dp[i-1][j]+dp[i][j-1]
Partition/MCMtry split k in [i,j]
Stocks(day, holding, txns left)
Tree DPreturn (incl, excl) per node
Bitmask (TSP)dp[mask][i] visited set
from functools import lru_cache
@lru_cache(None)
def knap(i, cap):
    if i==n or cap==0: return 0
    best = knap(i+1, cap)                 # skip
    if wt[i] <= cap:
        best = max(best, val[i]+knap(i+1, cap-wt[i]))
    return best

# LIS in O(n log n)
import bisect
def lis(a):
    tails=[]
    for x in a:
        i=bisect.bisect_left(tails, x)
        if i==len(tails): tails.append(x)
        else: tails[i]=x
    return len(tails)

🎯 Greedy

When: a locally-optimal choice provably leads to a global optimum. Usually sort first.
PATTERNS Intervals → sort by end, pick earliest finish. Fractional knapsack → sort by value/weight. Huffman → repeatedly merge two smallest (heap). Jump game → track farthest reach.

🔟 Bit Manipulation

IDENTITIES x&(x-1) clears lowest set bit · x&-x isolates it · a^a=0, a^0=a · x&(x-1)==0 ⇒ power of two.
  • Single number: XOR all → unpaired survives.
  • Count set bits: loop x&=x-1, or bin(x).count('1').
  • Subsets: iterate mask in range(1<<n).
  • Check/set/clear bit i: x>>i&1, x|1<<i, x&~(1<<i).
Iterate submasks of mask:
  sub = mask
  while sub:
      use(sub); sub = (sub-1) & mask

⭐ Must-Know Algorithms

One-line recall of the classic named algorithms interviewers expect.
AlgorithmIdeaComplexity
Kadanemax subarray: cur=max(x,cur+x)O(n)
Boyer–Mooremajority vote (count++/--) O(n), O(1)
Dutch National Flag3-way partition low/mid/highO(n)
Floyd's Cycletortoise & hare; reset to find startO(n), O(1)
Quickselectpartition around pivot for KthO(n) avg
Merge Sortdivide, sort, merge; count inversionsO(n log n)
KMPLPS array skips re-matchingO(n+m)
Rabin–Karprolling hash of windowsO(n+m) avg
Dijkstragreedy + min-heap, non-neg weightsO(E log V)
Bellman–Fordrelax edges V−1×; detect neg cycleO(V·E)
Floyd–Warshallall-pairs via intermediate kO(V³)
Kahn's TopoBFS on in-degree = 0O(V+E)
Kruskal / PrimMST via DSU / heapO(E log E)
Tarjan / Kosarajubridges, articulation, SCCO(V+E)
Union-Findpath compression + rank~O(α)≈O(1)
Morris Traversalthreaded inorder, O(1) spaceO(n)
Sieve of Eratosthenesmark multiples of each primeO(n log log n)
Fast Exponentiationsquare & multiply on bits of nO(log n)

🔢 Math & Number Theory

  • GCD (Euclid): gcd(a,b)=gcd(b,a%b); LCM = a*b/gcd.
  • Modular: (a+b)%m, (a*b)%m; use pow(a,b,m) for modular power.
  • Primes: sieve up to n; a number n is prime if no divisor ≤ √n.
  • Combinatorics: nCr = n!/(r!(n−r)!); Pascal for small n.
  • Overflow: Python ints are arbitrary precision (no overflow), but state it in other languages.
def sieve(n):
    is_p=[True]*(n+1); is_p[0]=is_p[1]=False
    for i in range(2, int(n**0.5)+1):
        if is_p[i]:
            for j in range(i*i, n+1, i): is_p[j]=False
    return [i for i,v in enumerate(is_p) if v]

def power(a, b, mod):
    res=1; a%=mod
    while b:
        if b&1: res=res*a%mod
        a=a*a%mod; b>>=1
    return res

🔃 Sorting

AlgorithmTimeSpaceStableNote
MergeO(n log n)O(n)Yeslinked lists, inversions
QuickO(n log n) avgO(log n)Noin-place; worst O(n²)
HeapO(n log n)O(1)Nono recursion
CountingO(n+k)O(k)Yessmall int range
RadixO(d·(n+b))O(n+b)Yesfixed-width keys
Cyclic sortO(n)O(1)Novalues in 1..n
Quickselect finds the Kth element in avg O(n), partition, recurse into one side only.

✅ Interview Playbook

Trigger → tool: sorted/pair → Two Pointers · contiguous subarray → Sliding Window / Prefix · next greater/spans → Monotonic Stack · shortest unweighted → BFS · weighted → Dijkstra · all combinations → Backtracking · min/max/count ways → DP · Top-K/Kth → Heap · prefix words → Trie · connectivity → Union-Find · ordering with deps → Topological Sort.