Expert Problem Solving
Master the most challenging algorithmic problems with advanced techniques and sophisticated solutions
Level 5: Expert Interview Preparation - Expert Problem Solving
Hard Dynamic Programming Variants
Expert-level DP problems involve complex state spaces, multiple dimensions, optimization with constraints, and advanced techniques like digit DP and DP on trees/graphs.
🧩 Real-Life Analogy:
Think of planning a complex multi-city business trip with budget constraints, time limits, and varying costs. You need to consider multiple factors simultaneously and find the optimal path. Hard DP problems require similar multi-dimensional thinking.
TypeScript
// Digit DP: Count numbers with specific properties function countNumbersWithProperty(n: string): number { const digits = n.split('').map(Number); const memo = new Map(); function dp(pos: number, tight: boolean, started: boolean, sum: number): number { if (pos === digits.length) { return started ? (sum % 3 === 0 ? 1 : 0) : 0; } const key = `${pos}-${tight}-${started}-${sum}`; if (memo.has(key)) return memo.get(key)!; const limit = tight ? digits[pos] : 9; let result = 0; for (let digit = 0; digit <= limit; digit++) { const newTight = tight && (digit === limit); const newStarted = started || (digit > 0); const newSum = sum + digit; result += dp(pos + 1, newTight, newStarted, newSum); } memo.set(key, result); return result; } return dp(0, true, false, 0); } // DP on Trees: Maximum sum with no adjacent nodes class TreeNode { val: number; children: TreeNode[]; constructor(val: number) { this.val = val; this.children = []; } } function robTree(root: TreeNode | null): number { if (!root) return 0; function dfs(node: TreeNode | null): [number, number] { if (!node) return [0, 0]; let withoutCurrent = 0; let withCurrent = node.val; for (const child of node.children) { const [childWithout, childWith] = dfs(child); withoutCurrent += Math.max(childWithout, childWith); withCurrent += childWithout; } return [withoutCurrent, withCurrent]; } const [without, with_] = dfs(root); return Math.max(without, with_); } // Bitmask DP: Traveling Salesman Problem function tsp(graph: number[][]): number { const n = graph.length; const memo = Array(1 << n).fill(null).map(() => Array(n).fill(-1)); function dp(mask: number, pos: number): number { if (mask === (1 << n) - 1) { return graph[pos][0]; // Return to start } if (memo[mask][pos] !== -1) { return memo[mask][pos]; } let result = Infinity; for (let city = 0; city < n; city++) { if (!(mask & (1 << city))) { const newMask = mask | (1 << city); const cost = graph[pos][city] + dp(newMask, city); result = Math.min(result, cost); } } memo[mask][pos] = result; return result; } return dp(1, 0); // Start from city 0 } // Matrix Chain Multiplication with optimal parenthesization function matrixChainOrder(dimensions: number[]): [number, string] { const n = dimensions.length - 1; const dp = Array(n).fill(null).map(() => Array(n).fill(0)); const split = Array(n).fill(null).map(() => Array(n).fill(0)); // l is chain length for (let l = 2; l <= n; l++) { for (let i = 0; i <= n - l; i++) { const j = i + l - 1; dp[i][j] = Infinity; for (let k = i; k < j; k++) { const cost = dp[i][k] + dp[k + 1][j] + dimensions[i] * dimensions[k + 1] * dimensions[j + 1]; if (cost < dp[i][j]) { dp[i][j] = cost; split[i][j] = k; } } } } function buildParentheses(i: number, j: number): string { if (i === j) return `M${i}`; return `(${buildParentheses(i, split[i][j])} * ${buildParentheses(split[i][j] + 1, j)})`; } return [dp[0][n - 1], buildParentheses(0, n - 1)]; } // Advanced DP: Edit Distance with operations cost function editDistanceWithCosts(s1: string, s2: string, insertCost: number, deleteCost: number, replaceCost: number): number { const m = s1.length, n = s2.length; const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0)); // Initialize base cases for (let i = 0; i <= m; i++) { dp[i][0] = i * deleteCost; } for (let j = 0; j <= n; j++) { dp[0][j] = j * insertCost; } for (let i = 1; i <= m; i++) { for (let j = 1; j <= n; j++) { if (s1[i - 1] === s2[j - 1]) { dp[i][j] = dp[i - 1][j - 1]; } else { dp[i][j] = Math.min( dp[i - 1][j] + deleteCost, // Delete dp[i][j - 1] + insertCost, // Insert dp[i - 1][j - 1] + replaceCost // Replace ); } } } return dp[m][n]; }
Python
from functools import lru_cache from typing import List, Tuple # Digit DP: Count numbers with specific properties def count_numbers_with_property(n: str) -> int: digits = [int(d) for d in n] @lru_cache(None) def dp(pos: int, tight: bool, started: bool, digit_sum: int) -> int: if pos == len(digits): return 1 if started and digit_sum % 3 == 0 else 0 limit = digits[pos] if tight else 9 result = 0 for digit in range(limit + 1): new_tight = tight and (digit == limit) new_started = started or (digit > 0) new_sum = digit_sum + digit result += dp(pos + 1, new_tight, new_started, new_sum) return result return dp(0, True, False, 0) # DP on Trees: Maximum sum with no adjacent nodes class TreeNode: def __init__(self, val=0): self.val = val self.children = [] def rob_tree(root: TreeNode) -> int: def dfs(node): if not node: return (0, 0) # (without_current, with_current) without_current = 0 with_current = node.val for child in node.children: child_without, child_with = dfs(child) without_current += max(child_without, child_with) with_current += child_without return (without_current, with_current) if not root: return 0 without, with_node = dfs(root) return max(without, with_node) # Bitmask DP: Traveling Salesman Problem def tsp(graph: List[List[int]]) -> int: n = len(graph) memo = {} def dp(mask: int, pos: int) -> int: if mask == (1 << n) - 1: return graph[pos][0] # Return to start if (mask, pos) in memo: return memo[(mask, pos)] result = float('inf') for city in range(n): if not (mask & (1 << city)): new_mask = mask | (1 << city) cost = graph[pos][city] + dp(new_mask, city) result = min(result, cost) memo[(mask, pos)] = result return result return dp(1, 0) # Start from city 0 # Matrix Chain Multiplication with optimal parenthesization def matrix_chain_order(dimensions: List[int]) -> Tuple[int, str]: n = len(dimensions) - 1 dp = [[0] * n for _ in range(n)] split = [[0] * n for _ in range(n)] # l is chain length for l in range(2, n + 1): for i in range(n - l + 1): j = i + l - 1 dp[i][j] = float('inf') for k in range(i, j): cost = (dp[i][k] + dp[k + 1][j] + dimensions[i] * dimensions[k + 1] * dimensions[j + 1]) if cost < dp[i][j]: dp[i][j] = cost split[i][j] = k def build_parentheses(i: int, j: int) -> str: if i == j: return f"M{i}" return f"({build_parentheses(i, split[i][j])} * {build_parentheses(split[i][j] + 1, j)})" return dp[0][n - 1], build_parentheses(0, n - 1) # Advanced DP: Edit Distance with operations cost def edit_distance_with_costs(s1: str, s2: str, insert_cost: int, delete_cost: int, replace_cost: int) -> int: m, n = len(s1), len(s2) dp = [[0] * (n + 1) for _ in range(m + 1)] # Initialize base cases for i in range(m + 1): dp[i][0] = i * delete_cost for j in range(n + 1): dp[0][j] = j * insert_cost for i in range(1, m + 1): for j in range(1, n + 1): if s1[i - 1] == s2[j - 1]: dp[i][j] = dp[i - 1][j - 1] else: dp[i][j] = min( dp[i - 1][j] + delete_cost, # Delete dp[i][j - 1] + insert_cost, # Insert dp[i - 1][j - 1] + replace_cost # Replace ) return dp[m][n] # DP with Convex Hull Trick: Optimize line intersection queries class ConvexHullTrick: def __init__(self): self.lines = [] # (slope, intercept, id) def bad(self, l1, l2, l3): # Check if l2 is redundant return ((l3[1] - l1[1]) * (l1[0] - l2[0]) <= (l2[1] - l1[1]) * (l1[0] - l3[0])) def add_line(self, slope, intercept, line_id=0): new_line = (slope, intercept, line_id) while (len(self.lines) >= 2 and self.bad(self.lines[-2], self.lines[-1], new_line)): self.lines.pop() self.lines.append(new_line) def query(self, x): if not self.lines: return float('inf') # Binary search for best line left, right = 0, len(self.lines) - 1 while left < right: mid = (left + right) // 2 val1 = self.lines[mid][0] * x + self.lines[mid][1] val2 = self.lines[mid + 1][0] * x + self.lines[mid + 1][1] if val1 > val2: left = mid + 1 else: right = mid line = self.lines[left] return line[0] * x + line[1]
OCaml
open Printf (* Digit DP: Count numbers with specific properties *) let count_numbers_with_property n = let digits = Array.init (String.length n) (fun i -> int_of_char n.[i] - int_of_char '0') in let memo = Hashtbl.create 1000 in let rec dp pos tight started digit_sum = if pos = Array.length digits then if started && digit_sum mod 3 = 0 then 1 else 0 else let key = (pos, tight, started, digit_sum) in match Hashtbl.find_opt memo key with | Some result -> result | None -> let limit = if tight then digits.(pos) else 9 in let result = ref 0 in for digit = 0 to limit do let new_tight = tight && (digit = limit) in let new_started = started || (digit > 0) in let new_sum = digit_sum + digit in result := !result + dp (pos + 1) new_tight new_started new_sum done; Hashtbl.add memo key !result; !result in dp 0 true false 0 (* DP on Trees: Maximum sum with no adjacent nodes *) type tree_node = { value: int; children: tree_node list; } let rob_tree root = let rec dfs node = match node with | None -> (0, 0) (* (without_current, with_current) *) | Some n -> let without_current = ref 0 in let with_current = ref n.value in List.iter (fun child -> let child_without, child_with = dfs (Some child) in without_current := !without_current + max child_without child_with; with_current := !with_current + child_without ) n.children; (!without_current, !with_current) in match root with | None -> 0 | Some _ -> let without, with_node = dfs root in max without with_node (* Bitmask DP: Traveling Salesman Problem *) let tsp graph = let n = Array.length graph in let memo = Hashtbl.create (1 lsl n * n) in let rec dp mask pos = if mask = (1 lsl n) - 1 then graph.(pos).(0) (* Return to start *) else match Hashtbl.find_opt memo (mask, pos) with | Some result -> result | None -> let result = ref max_int in for city = 0 to n - 1 do if mask land (1 lsl city) = 0 then ( let new_mask = mask lor (1 lsl city) in let cost = graph.(pos).(city) + dp new_mask city in result := min !result cost ) done; Hashtbl.add memo (mask, pos) !result; !result in dp 1 0 (* Start from city 0 *) (* Matrix Chain Multiplication *) let matrix_chain_order dimensions = let n = Array.length dimensions - 1 in let dp = Array.make_matrix n n 0 in let split = Array.make_matrix n n 0 in (* l is chain length *) for l = 2 to n do for i = 0 to n - l do let j = i + l - 1 in dp.(i).(j) <- max_int; for k = i to j - 1 do let cost = dp.(i).(k) + dp.(k + 1).(j) + dimensions.(i) * dimensions.(k + 1) * dimensions.(j + 1) in if cost < dp.(i).(j) then ( dp.(i).(j) <- cost; split.(i).(j) <- k ) done done done; let rec build_parentheses i j = if i = j then sprintf "M%d" i else sprintf "(%s * %s)" (build_parentheses i split.(i).(j)) (build_parentheses (split.(i).(j) + 1) j) in (dp.(0).(n - 1), build_parentheses 0 (n - 1)) (* Edit Distance with costs *) let edit_distance_with_costs s1 s2 insert_cost delete_cost replace_cost = let m = String.length s1 and n = String.length s2 in let dp = Array.make_matrix (m + 1) (n + 1) 0 in (* Initialize base cases *) for i = 0 to m do dp.(i).(0) <- i * delete_cost done; for j = 0 to n do dp.(0).(j) <- j * insert_cost done; for i = 1 to m do for j = 1 to n do if s1.[i - 1] = s2.[j - 1] then dp.(i).(j) <- dp.(i - 1).(j - 1) else dp.(i).(j) <- min (min (dp.(i - 1).(j) + delete_cost) (* Delete *) (dp.(i).(j - 1) + insert_cost)) (* Insert *) (dp.(i - 1).(j - 1) + replace_cost) (* Replace *) done done; dp.(m).(n)
🧠 Advanced DP Techniques:
- Digit DP: Count numbers with specific digit properties
- Bitmask DP: Exponential state spaces using bit manipulation
- DP on Trees: Optimal solutions considering tree structure
- Convex Hull Trick: Optimize linear function queries
- Divide & Conquer DP: Reduce complexity with monotonicity
Complex Graph Algorithms
Advanced graph problems involving flow networks, matching algorithms, strongly connected components, and sophisticated traversal techniques.
🌐 Real-Life Analogy:
Think of managing a complex transportation network during peak hours - you need to handle maximum flow through highways, match drivers to routes optimally, detect traffic circles, and find strongly connected regions. Advanced graph algorithms solve these intricate network problems.
TypeScript
// Ford-Fulkerson Algorithm for Maximum Flow class MaxFlow { private graph: number[][]; private rGraph: number[][]; private parent: number[]; private n: number; constructor(capacity: number[][]) { this.n = capacity.length; this.graph = capacity.map(row => [...row]); this.rGraph = capacity.map(row => [...row]); this.parent = new Array(this.n); } private bfs(source: number, sink: number): boolean { const visited = new Array(this.n).fill(false); const queue: number[] = [source]; visited[source] = true; this.parent[source] = -1; while (queue.length > 0) { const u = queue.shift()!; for (let v = 0; v < this.n; v++) { if (!visited[v] && this.rGraph[u][v] > 0) { visited[v] = true; this.parent[v] = u; queue.push(v); if (v === sink) return true; } } } return false; } fordFulkerson(source: number, sink: number): number { let maxFlow = 0; while (this.bfs(source, sink)) { let pathFlow = Infinity; // Find minimum residual capacity along the path for (let v = sink; v !== source; v = this.parent[v]) { const u = this.parent[v]; pathFlow = Math.min(pathFlow, this.rGraph[u][v]); } // Add path flow to overall flow maxFlow += pathFlow; // Update residual capacities for (let v = sink; v !== source; v = this.parent[v]) { const u = this.parent[v]; this.rGraph[u][v] -= pathFlow; this.rGraph[v][u] += pathFlow; } } return maxFlow; } } // Dinic's Algorithm for Maximum Flow (O(V²E)) class Dinic { private graph: Map>; private level: number[]; private iter: number[]; private n: number; constructor(n: number) { this.n = n; this.graph = new Map(); this.level = new Array(n); this.iter = new Array(n); for (let i = 0; i < n; i++) { this.graph.set(i, new Map()); } } addEdge(from: number, to: number, capacity: number): void { this.graph.get(from)!.set(to, (this.graph.get(from)!.get(to) || 0) + capacity); this.graph.get(to)!.set(from, this.graph.get(to)!.get(from) || 0); } private bfs(source: number, sink: number): boolean { this.level.fill(-1); this.level[source] = 0; const queue: number[] = [source]; while (queue.length > 0) { const v = queue.shift()!; for (const [to, capacity] of this.graph.get(v)!) { if (this.level[to] < 0 && capacity > 0) { this.level[to] = this.level[v] + 1; queue.push(to); } } } return this.level[sink] >= 0; } private dfs(v: number, sink: number, pushed: number): number { if (v === sink || pushed === 0) return pushed; const edges = this.graph.get(v)!; const edgeList = Array.from(edges.entries()); for (let i = this.iter[v]; i < edgeList.length; i++) { const [to, capacity] = edgeList[i]; if (this.level[v] + 1 !== this.level[to] || capacity <= 0) { continue; } const tr = this.dfs(to, sink, Math.min(pushed, capacity)); if (tr > 0) { edges.set(to, capacity - tr); this.graph.get(to)!.set(v, this.graph.get(to)!.get(v)! + tr); return tr; } } return 0; } maxFlow(source: number, sink: number): number { let flow = 0; while (this.bfs(source, sink)) { this.iter.fill(0); while (true) { const pushed = this.dfs(source, sink, Infinity); if (pushed === 0) break; flow += pushed; } } return flow; } } // Hungarian Algorithm for Bipartite Matching class HungarianAlgorithm { private cost: number[][]; private n: number; private u: number[]; private v: number[]; private p: number[]; private way: number[]; constructor(cost: number[][]) { this.n = cost.length; this.cost = cost.map(row => [...row]); this.u = new Array(this.n + 1).fill(0); this.v = new Array(this.n + 1).fill(0); this.p = new Array(this.n + 1).fill(0); this.way = new Array(this.n + 1).fill(0); } solve(): [number, number[]] { for (let i = 1; i <= this.n; i++) { this.p[0] = i; let j0 = 0; const minv = new Array(this.n + 1).fill(Infinity); const used = new Array(this.n + 1).fill(false); do { used[j0] = true; const i0 = this.p[j0]; let delta = Infinity; let j1 = 0; for (let j = 1; j <= this.n; j++) { if (!used[j]) { const cur = this.cost[i0 - 1][j - 1] - this.u[i0] - this.v[j]; if (cur < minv[j]) { minv[j] = cur; this.way[j] = j0; } if (minv[j] < delta) { delta = minv[j]; j1 = j; } } } for (let j = 0; j <= this.n; j++) { if (used[j]) { this.u[this.p[j]] += delta; this.v[j] -= delta; } else { minv[j] -= delta; } } j0 = j1; } while (this.p[j0] !== 0); do { const j1 = this.way[j0]; this.p[j0] = this.p[j1]; j0 = j1; } while (j0 !== 0); } const assignment = new Array(this.n).fill(-1); for (let j = 1; j <= this.n; j++) { if (this.p[j] !== 0) { assignment[this.p[j] - 1] = j - 1; } } return [-this.v[0], assignment]; } }
Python
from collections import deque, defaultdict from typing import List, Tuple, Dict import heapq # Ford-Fulkerson Algorithm for Maximum Flow class MaxFlow: def __init__(self, capacity: List[List[int]]): self.n = len(capacity) self.graph = [row[:] for row in capacity] self.r_graph = [row[:] for row in capacity] self.parent = [-1] * self.n def bfs(self, source: int, sink: int) -> bool: visited = [False] * self.n queue = deque([source]) visited[source] = True while queue: u = queue.popleft() for v in range(self.n): if not visited[v] and self.r_graph[u][v] > 0: visited[v] = True self.parent[v] = u queue.append(v) if v == sink: return True return False def ford_fulkerson(self, source: int, sink: int) -> int: max_flow = 0 while self.bfs(source, sink): path_flow = float('inf') # Find minimum residual capacity along the path s = sink while s != source: path_flow = min(path_flow, self.r_graph[self.parent[s]][s]) s = self.parent[s] # Add path flow to overall flow max_flow += path_flow # Update residual capacities v = sink while v != source: u = self.parent[v] self.r_graph[u][v] -= path_flow self.r_graph[v][u] += path_flow v = self.parent[v] return max_flow # Dinic's Algorithm for Maximum Flow class Dinic: def __init__(self, n: int): self.n = n self.graph = defaultdict(lambda: defaultdict(int)) self.level = [-1] * n self.iter = [0] * n def add_edge(self, from_node: int, to_node: int, capacity: int): self.graph[from_node][to_node] += capacity self.graph[to_node][from_node] += 0 # reverse edge def bfs(self, source: int, sink: int) -> bool: self.level = [-1] * self.n self.level[source] = 0 queue = deque([source]) while queue: v = queue.popleft() for to_node, capacity in self.graph[v].items(): if self.level[to_node] < 0 and capacity > 0: self.level[to_node] = self.level[v] + 1 queue.append(to_node) return self.level[sink] >= 0 def dfs(self, v: int, sink: int, pushed: int) -> int: if v == sink or pushed == 0: return pushed edges = list(self.graph[v].items()) for i in range(self.iter[v], len(edges)): to_node, capacity = edges[i] if self.level[v] + 1 != self.level[to_node] or capacity <= 0: continue tr = self.dfs(to_node, sink, min(pushed, capacity)) if tr > 0: self.graph[v][to_node] -= tr self.graph[to_node][v] += tr return tr return 0 def max_flow(self, source: int, sink: int) -> int: flow = 0 while self.bfs(source, sink): self.iter = [0] * self.n while True: pushed = self.dfs(source, sink, float('inf')) if pushed == 0: break flow += pushed return flow # Hungarian Algorithm for Bipartite Matching class HungarianAlgorithm: def __init__(self, cost_matrix: List[List[int]]): self.n = len(cost_matrix) self.cost = [row[:] for row in cost_matrix] self.u = [0] * (self.n + 1) self.v = [0] * (self.n + 1) self.p = [0] * (self.n + 1) self.way = [0] * (self.n + 1) def solve(self) -> Tuple[int, List[int]]: for i in range(1, self.n + 1): self.p[0] = i j0 = 0 minv = [float('inf')] * (self.n + 1) used = [False] * (self.n + 1) while True: used[j0] = True i0 = self.p[j0] delta = float('inf') j1 = 0 for j in range(1, self.n + 1): if not used[j]: cur = self.cost[i0 - 1][j - 1] - self.u[i0] - self.v[j] if cur < minv[j]: minv[j] = cur self.way[j] = j0 if minv[j] < delta: delta = minv[j] j1 = j for j in range(self.n + 1): if used[j]: self.u[self.p[j]] += delta self.v[j] -= delta else: minv[j] -= delta j0 = j1 if self.p[j0] == 0: break while j0 != 0: j1 = self.way[j0] self.p[j0] = self.p[j1] j0 = j1 assignment = [-1] * self.n for j in range(1, self.n + 1): if self.p[j] != 0: assignment[self.p[j] - 1] = j - 1 return -self.v[0], assignment
OCaml
open Printf (* Ford-Fulkerson Algorithm for Maximum Flow *) module MaxFlow = struct type t = { n: int; graph: int array array; r_graph: int array array; parent: int array; } let create capacity = let n = Array.length capacity in { n; graph = Array.map Array.copy capacity; r_graph = Array.map Array.copy capacity; parent = Array.make n (-1); } let bfs flow source sink = let visited = Array.make flow.n false in let queue = Queue.create () in Queue.add source queue; visited.(source) <- true; flow.parent.(source) <- -1; let found = ref false in while not (Queue.is_empty queue) && not !found do let u = Queue.take queue in for v = 0 to flow.n - 1 do if not visited.(v) && flow.r_graph.(u).(v) > 0 then begin visited.(v) <- true; flow.parent.(v) <- u; Queue.add v queue; if v = sink then found := true end done done; !found let ford_fulkerson flow source sink = let max_flow = ref 0 in while bfs flow source sink do let path_flow = ref max_int in (* Find minimum residual capacity along the path *) let s = ref sink in while !s <> source do let u = flow.parent.(!s) in path_flow := min !path_flow flow.r_graph.(u).(!s); s := flow.parent.(!s) done; (* Add path flow to overall flow *) max_flow := !max_flow + !path_flow; (* Update residual capacities *) let v = ref sink in while !v <> source do let u = flow.parent.(!v) in flow.r_graph.(u).(!v) <- flow.r_graph.(u).(!v) - !path_flow; flow.r_graph.(!v).(u) <- flow.r_graph.(!v).(u) + !path_flow; v := flow.parent.(!v) done done; !max_flow end (* Hungarian Algorithm *) module Hungarian = struct type t = { n: int; cost: int array array; u: int array; v: int array; p: int array; way: int array; } let create cost_matrix = let n = Array.length cost_matrix in { n; cost = Array.map Array.copy cost_matrix; u = Array.make (n + 1) 0; v = Array.make (n + 1) 0; p = Array.make (n + 1) 0; way = Array.make (n + 1) 0; } let solve hungarian = for i = 1 to hungarian.n do hungarian.p.(0) <- i; let j0 = ref 0 in let minv = Array.make (hungarian.n + 1) max_int in let used = Array.make (hungarian.n + 1) false in while hungarian.p.(!j0) <> 0 do used.(!j0) <- true; let i0 = hungarian.p.(!j0) in let delta = ref max_int in let j1 = ref 0 in for j = 1 to hungarian.n do if not used.(j) then begin let cur = hungarian.cost.(i0 - 1).(j - 1) - hungarian.u.(i0) - hungarian.v.(j) in if cur < minv.(j) then begin minv.(j) <- cur; hungarian.way.(j) <- !j0 end; if minv.(j) < !delta then begin delta := minv.(j); j1 := j end end done; for j = 0 to hungarian.n do if used.(j) then begin hungarian.u.(hungarian.p.(j)) <- hungarian.u.(hungarian.p.(j)) + !delta; hungarian.v.(j) <- hungarian.v.(j) - !delta end else minv.(j) <- minv.(j) - !delta done; j0 := !j1 done; let j = ref !j0 in while !j <> 0 do let j1 = hungarian.way.(!j) in hungarian.p.(!j) <- hungarian.p.(j1); j := j1 done done; let assignment = Array.make hungarian.n (-1) in for j = 1 to hungarian.n do if hungarian.p.(j) <> 0 then assignment.(hungarian.p.(j) - 1) <- j - 1 done; (-hungarian.v.(0), assignment) end
🔗 Advanced Graph Concepts:
- Maximum Flow: Ford-Fulkerson, Dinic's algorithm
- Bipartite Matching: Hungarian algorithm, Hopcroft-Karp
- Network Flow: Min-cost max-flow, circulation
- Strong Components: Tarjan's and Kosaraju's algorithms
- Heavy-Light Decomposition: Tree path queries
Algorithm | Time Complexity | Space Complexity | Use Case |
---|---|---|---|
Ford-Fulkerson | O(E × max_flow) | O(V²) | General maximum flow |
Dinic's Algorithm | O(V² × E) | O(V + E) | Faster max flow |
Hungarian Algorithm | O(V³) | O(V²) | Min-cost bipartite matching |
Advanced String Algorithms
Sophisticated string processing techniques including pattern matching, suffix structures, and string compression algorithms.
📚 Real-Life Analogy:
Think of a advanced search engine that can find patterns in billions of documents instantly, detect plagiarism, compress text efficiently, and build indexes for lightning-fast queries. Advanced string algorithms power these capabilities.
🔤 String Algorithm Techniques:
- KMP Algorithm: Linear pattern matching with prefix function
- Z-Algorithm: Linear time string matching
- Suffix Arrays: Space-efficient suffix structure
- Manacher's Algorithm: Linear palindrome detection
- Aho-Corasick: Multiple pattern matching
Geometric Algorithms
Computational geometry algorithms for solving problems involving points, lines, polygons, and higher-dimensional structures.
🎯 Real-Life Analogy:
Think of a GPS navigation system that needs to find the shortest path around obstacles, determine if you're inside a restricted area, or calculate the closest gas station. Geometric algorithms handle these spatial reasoning problems.
📐 Geometric Concepts:
- Convex Hull: Graham scan, gift wrapping
- Line Intersection: Sweep line algorithms
- Point in Polygon: Ray casting, winding number
- Closest Pair: Divide and conquer approach
- Voronoi Diagrams: Spatial partitioning
Ready to excel in interviews?
You've mastered complex problem-solving! Now let's focus on interview excellence and presentation skills.
Continue to Interview Excellence →