DSA Fundamentals

61DSA Fundamentals (deep teach)

What you will master here

  • Complexity analysis (Big-O, Big-Θ, Big-Ω, amortised)
  • Arrays, strings, hashmaps, sets — when to pick each
  • Stacks, queues, deques, priority queues
  • Linked lists, doubly-linked lists
  • Trees: binary, BST, balanced (AVL/Red-Black), trie
  • Graphs: BFS, DFS, Dijkstra, topological sort
  • Sorting: quicksort, mergesort, heapsort, counting sort
  • Recursion vs iteration; memoisation; DP

61.1 Complexity — the alphabet

NotationMeaning
Big-O — O(f)Upper bound — "grows no faster than f"
Big-Ω — Ω(f)Lower bound — "grows at least as fast as f"
Big-Θ — Θ(f)Tight bound — both upper and lower
AmortisedAverage over a sequence — array push is O(1) amortised even though resize is O(n)
ComplexityExamplesFeels like
O(1)Hash lookup, array indexInstant
O(log n)Binary search, balanced tree ops1B items in ~30 steps
O(n)Linear scanScales linearly
O(n log n)Mergesort, heapsortBest general-purpose sort
O(n²)Nested loop, bubble sortSlow on big input
O(2ⁿ)Naive subset/FibonacciExponential — usually wrong
O(n!)All permutationsBrute force; need pruning

61.2 Arrays & Strings

// Prefix sum
function prefix(nums: number[]): number[] {
  const p = new Array(nums.length + 1).fill(0);
  for (let i = 0; i < nums.length; i++) p[i + 1] = p[i] + nums[i];
  return p;
}
// rangeSum(l, r) = p[r+1] - p[l]

61.3 Hashmaps & Sets

61.4 Stacks & Queues

StructureOrderOperations
StackLIFOpush, pop, peek (top) — all O(1)
QueueFIFOenqueue, dequeue — O(1) with linked list
DequeBoth endspushFront, pushBack, popFront, popBack — O(1)
Priority Queue (min-heap)By priorityinsert, extract-min — O(log n)

JS arrays approximate all four — push/pop for stack, push/shift for queue (but shift is O(n)!). For real queue use a linked list or a JS class.

61.5 Linked Lists

class Node { val: number; next: Node | null = null; constructor(v: number) { this.val = v; } }

// Insert at head — O(1)
function prepend(head: Node | null, v: number): Node {
  const n = new Node(v); n.next = head; return n;
}

// Reverse iteratively — O(n) time, O(1) space
function reverse(head: Node | null): Node | null {
  let prev: Node | null = null, curr = head;
  while (curr) { const next = curr.next; curr.next = prev; prev = curr; curr = next; }
  return prev;
}

When to use a linked list: O(1) insert/delete at known positions; no random access required. Most real code uses arrays — linked lists are interview territory.

61.6 Trees

Tree typePropertyUsed for
Binary treeEach node ≤ 2 childrenGeneric hierarchical data
Binary search tree (BST)Left < root < rightSorted lookup O(log n) when balanced
Balanced BST (AVL, Red-Black)Auto-balancingJava TreeMap, C++ std::map
Heap (binary)Parent ≤ children (min-heap)Priority queue
Trie (prefix tree)Path = stringAutocomplete, prefix queries
B-treeMany children per nodeDB indexes, filesystems

Tree traversals

// DFS preorder
function preorder(n: TreeNode | null, out: number[] = []): number[] {
  if (!n) return out;
  out.push(n.val);
  preorder(n.left, out);
  preorder(n.right, out);
  return out;
}

// BFS
function bfs(root: TreeNode | null): number[] {
  const out: number[] = [], q: TreeNode[] = root ? [root] : [];
  while (q.length) {
    const n = q.shift()!;
    out.push(n.val);
    if (n.left) q.push(n.left);
    if (n.right) q.push(n.right);
  }
  return out;
}

61.7 Graphs

A graph = nodes + edges. Directed or undirected, weighted or not, cyclic or acyclic.

Representations

Algorithms

AlgorithmPurposeComplexity
BFSShortest path (unweighted)O(V + E)
DFSConnectivity, topological sortO(V + E)
DijkstraShortest path (positive weights)O((V+E) log V)
Bellman-FordShortest path (negative weights)O(V·E)
Floyd-WarshallAll-pairs shortest pathO(V³)
Topological sortDAG orderingO(V + E)
Union-FindConnected componentsNearly O(1) per op

61.8 Sorting

AlgorithmTime avgTime worstStable?
Bubble / InsertionO(n²)O(n²)Yes
MergesortO(n log n)O(n log n)Yes
QuicksortO(n log n)O(n²)No (typically)
HeapsortO(n log n)O(n log n)No
Counting / RadixO(n + k)O(n + k)Yes
JS Array.sort (V8)O(n log n)O(n log n)Yes (Timsort since 2019)

61.9 Recursion & DP

Recursion basics

Memoisation (top-down DP)

// Fibonacci with memo
const memo = new Map<number, number>();
function fib(n: number): number {
  if (n < 2) return n;
  if (memo.has(n)) return memo.get(n)!;
  const r = fib(n - 1) + fib(n - 2);
  memo.set(n, r);
  return r;
}
// O(n) with memo vs O(2^n) without

Tabulation (bottom-up DP)

function fib2(n: number): number {
  if (n < 2) return n;
  let a = 0, b = 1;
  for (let i = 2; i <= n; i++) [a, b] = [b, a + b];
  return b;
}
// O(n) time, O(1) space

61.10 When to use which

NeedPick
Random access by indexArray
Key → value lookupHashMap
Membership checkSet
LIFO orderStack (array)
FIFO orderQueue (linked list / deque)
Priority accessHeap / PriorityQueue
Sorted lookupBalanced BST (TreeMap)
Prefix searchTrie
Connected components / cycle detectionUnion-Find / DFS
Shortest path unweightedBFS
Shortest path weightedDijkstra

Module 37 — DSA Q&A

What's the difference between O, Θ, Ω?
Big-O: upper bound (grows no faster than). Big-Ω: lower bound (grows at least as fast as). Big-Θ: both — tight bound. In practice O is what's usually meant.
What is amortised complexity?
Average cost per operation over a sequence. Array push is O(1) amortised: most pushes are O(1), occasional resizes are O(n), but spread over many pushes the average stays constant.
Hashmap vs balanced BST?
Hashmap: O(1) avg lookup, no order. BST (balanced): O(log n) lookup, maintains sorted order, supports range queries. Use BST when you need to iterate in order or query ranges.
When would you use a stack?
Anywhere you need LIFO: function call tracking, undo, expression evaluation, valid-parentheses, DFS without recursion. push/pop on a JS array works.
What's a priority queue?
Queue where each element has a priority; extract always returns highest priority. Usually implemented as a binary heap — O(log n) insert and extract. Used in Dijkstra, top-K, scheduling.
BFS or DFS — when?
BFS for shortest path on unweighted graphs / level-order on trees. DFS for full exploration, topological sort, cycle detection, path existence. BFS = queue; DFS = recursion or stack.
What's a trie?
Tree where each node represents a character; path from root to node spells a prefix. O(L) lookup where L = word length. Used in autocomplete, prefix queries, spell-checkers.
How does mergesort work?
Recursively split array in half, sort each half, merge. O(n log n) guaranteed, stable, but O(n) extra space. Quicksort is similar but in-place and faster in practice (cache-friendly).
What's the difference between memoisation and tabulation?
Memoisation: top-down recursion + cache. Tabulation: bottom-up iteration, fill a table. Same time complexity; tabulation often uses less memory (can drop earlier rows), no recursion stack.
When is DP the right tool?
Problems with overlapping subproblems AND optimal substructure (optimal answer composed of optimal sub-answers). Climbing stairs, coin change, edit distance, LCS, knapsack. If subproblems don't repeat, plain recursion / divide-and-conquer is fine.
What's a binary heap?
Array-backed tree where parent < children (min-heap) or parent > children (max-heap). Insert and extract-min in O(log n). Index math: parent of i is (i-1)/2; children are 2i+1 and 2i+2.
Sliding window vs two-pointer?
Both move pointers forward. Two-pointer often starts at opposite ends (sorted pair sum, palindrome). Sliding window keeps a contiguous range, growing/shrinking to satisfy a constraint (longest substring without repeats).
What's the space complexity of a recursive DFS?
O(depth) for the call stack — up to O(n) for a skewed tree, O(log n) for a balanced tree. Iterative DFS with an explicit stack has the same.
Why might a hashmap be O(n) in the worst case?
Adversarial keys causing every entry to land in the same bucket — degenerates to a linked list. Real hash functions + load-factor-triggered resizing make this practically impossible for non-adversarial workloads.
When is recursion preferred over iteration?
When the natural definition is recursive — tree/graph traversal, divide-and-conquer (mergesort), backtracking (permutations). For linear iteration, plain loops are clearer and have no stack risk.

61.11 Advanced data structures (dev-level)

Fenwick tree (Binary Indexed Tree)

O(log n) prefix sums with point updates. Compact array implementation.

class Fenwick {
  private bit: number[];
  constructor(n: number) { this.bit = new Array(n + 1).fill(0); }
  update(i: number, delta: number) {
    for (i++; i < this.bit.length; i += i & -i) this.bit[i] += delta;
  }
  query(i: number): number {           // prefix sum [0..i]
    let s = 0;
    for (i++; i > 0; i -= i & -i) s += this.bit[i];
    return s;
  }
}

Segment tree

Range queries (sum/min/max) + range updates with lazy propagation. O(log n) per op.

Union-Find (Disjoint Set Union)

class DSU {
  parent: number[]; rank: number[];
  constructor(n: number) {
    this.parent = Array.from({ length: n }, (_, i) => i);
    this.rank = new Array(n).fill(0);
  }
  find(x: number): number {
    if (this.parent[x] !== x) this.parent[x] = this.find(this.parent[x]);  // path compression
    return this.parent[x];
  }
  union(x: number, y: number): boolean {
    const rx = this.find(x), ry = this.find(y);
    if (rx === ry) return false;
    if (this.rank[rx] < this.rank[ry]) this.parent[rx] = ry;
    else if (this.rank[rx] > this.rank[ry]) this.parent[ry] = rx;
    else { this.parent[ry] = rx; this.rank[rx]++; }
    return true;
  }
}
// Nearly O(1) per op with both optimisations

Trie + Aho-Corasick

class Trie {
  children = new Map<string, Trie>();
  end = false;
  insert(word: string) {
    let n: Trie = this;
    for (const c of word) {
      if (!n.children.has(c)) n.children.set(c, new Trie());
      n = n.children.get(c)!;
    }
    n.end = true;
  }
  search(word: string): boolean {
    let n: Trie = this;
    for (const c of word) {
      if (!n.children.has(c)) return false;
      n = n.children.get(c)!;
    }
    return n.end;
  }
}
// Aho-Corasick = Trie + failure links → multi-pattern search in O(text + patterns + matches)

Bloom filter

Probabilistic membership: false positives possible, false negatives impossible. Tiny memory. K hash functions set K bits per insert; lookup checks all K bits.

Skip list

Probabilistic alternative to balanced trees. Multiple levels of forward pointers. Average O(log n) ops. Used in Redis sorted sets, LevelDB.

LRU cache (O(1) all ops)

Hashmap + doubly-linked list. Map keys → list nodes. Move to head on access; evict tail on overflow. (See dev ebook m41 for full implementation.)

61.12 Graph algorithms (advanced)

AlgorithmPurposeComplexity
DijkstraSingle-source shortest path (non-negative weights)O((V+E) log V) with heap
Bellman-FordSame but handles negative weights; detects negative cyclesO(V·E)
Floyd-WarshallAll-pairs shortest pathO(V³)
A*Heuristic-guided Dijkstra (maps, games)Depends on heuristic
Kruskal / PrimMinimum spanning treeO(E log V)
Topological sortDAG linearisation; dependency orderO(V + E)
Tarjan / KosarajuStrongly connected componentsO(V + E)
Max-flow (Dinic's)Network capacityO(V²·E)

61.13 Dynamic programming patterns (complete catalogue)

PatternExample problems
1D stateFibonacci, climb stairs, house robber, max subarray (Kadane)
2D stringEdit distance, LCS, regex match, distinct subsequences
Knapsack (0/1)Subset sum, partition equal sum, target sum
Knapsack (unbounded)Coin change ways, combinations
Interval DPMatrix chain multiplication, palindrome partition, burst balloons
State machineBest time to buy/sell stock (with cooldown / fees / k transactions)
Tree DPHouse robber III, longest path, diameter
Bitmask DPTSP for small N (N ≤ 20), assignment problem
Digit DPCount numbers in [L,R] with property
DP on graphsLongest path in DAG, count paths

61.14 Bit manipulation

x & (x - 1)        // clears lowest set bit (Brian Kernighan)
x & -x             // isolates lowest set bit
x | (1 << k)       // set bit k
x & ~(1 << k)      // clear bit k
x ^ (1 << k)       // toggle bit k
(x >> k) & 1       // read bit k
Number.isInteger(Math.log2(x))  // is x a power of 2

// Count set bits (popcount)
function popcount(x: number): number {
  let c = 0;
  while (x) { x &= x - 1; c++; }
  return c;
}

// Swap without temp
a ^= b; b ^= a; a ^= b;

61.15 Math you'll use

61.16 SDET-flavoured DSA scenarios (interview-real)

  1. Test selection algorithm — given a diff (set of changed files) + a coverage map (test → files covered), select minimum tests covering all changed files (set cover, greedy approximation)
  2. Flake detection — given a stream of test results (pass/fail per run), detect tests with failure rate > X% in sliding window of N (sliding window + counter)
  3. Dependency graph — given a Jenkins job DAG, find longest path (critical path), detect cycles (topological sort)
  4. Test ordering — schedule tests to minimise total wall-clock time across K parallel workers (LPT scheduling, NP-hard, greedy approx)
  5. Log-search — find requests matching a pattern in a 100 GB log file; can't load to memory (streaming + grep + parallel)
  6. Rate limiter — design a sliding-window counter (sorted set in Redis, or per-shard counters with aggregation)

Module 37 — Advanced DSA Q&A bank

What's a Fenwick tree useful for?
Range-sum (or other associative-op) queries on a mutable array. O(log n) for both point update and prefix query. Simpler than segment tree when you only need point updates and prefix queries.
Union-Find optimisations?
Path compression (flatten chains during find) + union by rank/size (attach smaller tree under larger). Combined: nearly O(1) amortised per op — inverse Ackermann α(n) which is < 5 for any realistic n.
When use a Trie?
Autocomplete, prefix queries, dictionary problems, IP routing tables (radix trie). O(L) lookup where L = word length. Memory cost: one node per char-position across all words.
What's Aho-Corasick?
Trie + failure links. Search for many patterns in text in one O(text + patterns + matches) pass. Used in grep, antivirus, intrusion detection.
Bloom filter trade-off?
Constant-size, very small. False positives possible (with tunable rate), no false negatives. Use to skip expensive lookups (DB, disk) when the answer is "definitely not here".
Dijkstra vs Bellman-Ford?
Dijkstra: faster, requires non-negative weights. Bellman-Ford: slower, handles negative weights, detects negative cycles. Both single-source.
How to detect a cycle in a directed graph?
DFS with 3-colour state (white = unvisited, grey = in current stack, black = done). If you reach a grey node, there's a cycle. O(V + E).
Topological sort algorithms?
Kahn's BFS (repeatedly pick nodes with indegree 0) or DFS-postorder reverse. Both O(V + E). Kahn's also detects cycles (if not all nodes processed).
When use A*?
Shortest path on a graph where you have a heuristic estimate of distance to goal (e.g. Euclidean for maps). A* explores fewer nodes than Dijkstra when the heuristic is good. Heuristic must be admissible (never overestimates).
Memoisation vs tabulation — practical difference?
Memoisation: top-down recursion + cache. Often more intuitive but stack-bound. Tabulation: bottom-up loop. Usually less memory (can compress dimensions). Same time complexity.
How would you compress DP state?
If dp[i] depends only on dp[i-1], use two variables instead of an array. If dp[i][j] depends only on dp[i-1][j-...], reduce 2D to 1D rolling.
0/1 knapsack vs unbounded knapsack?
0/1: each item once; loop items outer, weights inner DESCENDING. Unbounded: items reusable; loop items outer, weights inner ASCENDING. The loop direction prevents/enables re-use.
What's a state machine DP?
Define states (e.g. "holding stock", "not holding") + transitions. dp[i][state] = best by end of day i in state. Useful for buy/sell stock variations.
How to find SCCs?
Tarjan's (one DFS pass with low-link) or Kosaraju's (two DFS passes; second on transposed graph). O(V + E). Common in compiler analysis, social network communities.
Reservoir sampling — algorithm?
Pick K from a stream of unknown N, uniform probability. Fill reservoir with first K. For i >= K, generate r = rand(0..i); if r < K, replace reservoir[r] with item i. O(N) one pass, O(K) memory.
How to find Kth largest in an array?
Min-heap of size K → O(n log k). Or quickselect (Hoare partition) → O(n) average, O(n²) worst. Or sort → O(n log n) simplest.
Sliding-window vs two-pointer — same thing?
Sliding window is a specific two-pointer pattern where both move forward, expanding/shrinking to maintain a constraint. Generic two-pointer also covers opposite-end traversal (palindrome, sorted pair).
How to handle very deep recursion in JS?
JS doesn't optimise tail calls (despite spec). Convert to iteration with an explicit stack. Or increase stack via Node's --stack-size flag (rare).
What's the matroid property (greedy correctness)?
A condition under which a greedy choice always leads to optimal. If a problem is a matroid, greedy works. Example: minimum spanning tree (Kruskal). Hard to prove for new problems; often need DP fallback.
Why is union-find used in Kruskal?
Kruskal sorts edges by weight, picks each if it doesn't form a cycle. "Doesn't form a cycle" = endpoints in different components. Union-find answers + merges in nearly O(1).
How would you implement a thread-safe LRU cache?
Single lock around the map+list ops (simple, contention high). Or finer: lock-free using ConcurrentHashMap + AtomicReference for nodes (complex, paper-level). For most apps, simple lock is fine; if hot, shard the cache across N independent LRUs.
One key formula to remember for DP?
Define dp[state] precisely. Then write transition: dp[state] = function of dp[smaller_states]. Then base + iteration order. Most DP bugs are unclear state definition.
How would an SDET design a test selection algorithm?
Map files → tests (from coverage data). Given changed files = set S, find minimum tests covering S — Set Cover (NP-hard). Use greedy: repeatedly pick the test covering most uncovered files. Approximation ratio ln(n). Add nightly full run as safety net.
How to detect a sliding-window flake?
Per-test ring buffer of last N results. Compute failure rate = fails / N. Alert if > threshold. Implementation: hashmap test_id → ring buffer; update on each test result.