Advanced Algorithms

Level 3: Intermediate Advanced - Master sophisticated algorithms for complex problem solving

Advanced Algorithms
Advanced Graph Algorithms
DFS/BFS Variations & Topological Sort
Beyond basic traversal, DFS and BFS can be adapted for complex graph problems including cycle detection, topological sorting, and finding strongly connected components.
🗺️ Real-Life Analogy:
Think of topological sort as planning a course schedule: you must take prerequisites before advanced courses. DFS helps find the right order by exploring dependencies deeply.
🔍 Dijkstra's Algorithm Step-by-Step:
Graph: A→B(4), A→C(2), B→C(1), B→D(5), C→D(8), C→E(10), D→E(2)
Source: A, Find shortest paths to all vertices

Step 1: Initialize distances
   A    B    C    D    E
   0   ∞    ∞    ∞    ∞    ← A is source (distance 0)

Step 2: Process A (current = A, distance = 0)
   A    B    C    D    E
   0    4    2    ∞    ∞    ← Update neighbors of A
        ↑    ↑
     A→B(4) A→C(2)

Step 3: Process C (current = C, distance = 2)
   A    B    C    D    E
   0    3    2   10   12    ← Update via C: B=min(4,2+1)=3, D=2+8=10, E=2+10=12
        ↑         ↑    ↑
     C→B(1)    C→D(8) C→E(10)

Step 4: Process B (current = B, distance = 3)
   A    B    C    D    E
   0    3    2    8   12    ← Update via B: D=min(10,3+5)=8
                   ↑
                B→D(5)

Step 5: Process D (current = D, distance = 8)
   A    B    C    D    E
   0    3    2    8   10    ← Update via D: E=min(12,8+2)=10
                        ↑
                     D→E(2)

Final shortest paths from A:
A→A: 0, A→B: 3, A→C: 2, A→D: 8, A→E: 10
Paths: A, A→C→B, A→C, A→C→B→D, A→C→B→D→E
                                
🌍 Real-World Applications:
🗺️ GPS Navigation: Finding shortest routes between locations considering traffic and road conditions
🌐 Network Routing: Determining optimal paths for data packets across internet infrastructure
🎮 Game AI: Pathfinding for NPCs and optimal movement in game worlds
📊 Social Networks: Finding shortest connection paths between users (degrees of separation)
🏭 Supply Chain: Optimizing logistics and delivery routes to minimize cost and time

🔍 Advanced Graph Applications:

  • Topological Sort: Order tasks with dependencies
  • Cycle Detection: Find circular dependencies
  • Strongly Connected Components: Find groups with mutual reachability
  • Bipartite Checking: Two-coloring problems
TypeScript
// Bellman-Ford Algorithm for shortest paths with negative weights
class BellmanFord {
    findShortestPaths(edges: [number, number, number][], vertices: number, source: number): 
        { distances: number[], hasNegativeCycle: boolean } {
        const distances = new Array(vertices).fill(Infinity);
        distances[source] = 0;
        
        // Relax edges V-1 times
        for (let i = 0; i < vertices - 1; i++) {
            for (const [u, v, weight] of edges) {
                if (distances[u] !== Infinity && distances[u] + weight < distances[v]) {
                    distances[v] = distances[u] + weight;
                }
            }
        }
        
        // Check for negative cycles
        let hasNegativeCycle = false;
        for (const [u, v, weight] of edges) {
            if (distances[u] !== Infinity && distances[u] + weight < distances[v]) {
                hasNegativeCycle = true;
                break;
            }
        }
        
        return { distances, hasNegativeCycle };
    }
}

// Floyd-Warshall Algorithm for all-pairs shortest paths
class FloydWarshall {
    allPairsShortestPaths(matrix: number[][]): number[][] {
        const n = matrix.length;
        const dist = matrix.map(row => [...row]);
        
        // Initialize with infinity for no direct path
        for (let i = 0; i < n; i++) {
            for (let j = 0; j < n; j++) {
                if (i !== j && dist[i][j] === 0) {
                    dist[i][j] = Infinity;
                }
            }
        }
        
        // Try all intermediate vertices
        for (let k = 0; k < n; k++) {
            for (let i = 0; i < n; i++) {
                for (let j = 0; j < n; j++) {
                    if (dist[i][k] + dist[k][j] < dist[i][j]) {
                        dist[i][j] = dist[i][k] + dist[k][j];
                    }
                }
            }
        }
        
        return dist;
    }
}

// Topological Sort using DFS
class TopologicalSort {
    private visited: Set = new Set();
    private stack: number[] = [];
    
    topologicalSort(graph: Map): number[] {
        this.visited.clear();
        this.stack = [];
        
        for (const vertex of graph.keys()) {
            if (!this.visited.has(vertex)) {
                this.dfs(vertex, graph);
            }
        }
        
        return this.stack.reverse();
    }
    
    private dfs(vertex: number, graph: Map): void {
        this.visited.add(vertex);
        
        const neighbors = graph.get(vertex) || [];
        for (const neighbor of neighbors) {
            if (!this.visited.has(neighbor)) {
                this.dfs(neighbor, graph);
            }
        }
        
        this.stack.push(vertex);
    }
}
                                
Python
# Bellman-Ford Algorithm
class BellmanFord:
    def find_shortest_paths(self, edges, vertices, source):
        """Find shortest paths with negative edge detection"""
        distances = [float('inf')] * vertices
        distances[source] = 0
        
        # Relax edges V-1 times
        for _ in range(vertices - 1):
            for u, v, weight in edges:
                if distances[u] != float('inf') and distances[u] + weight < distances[v]:
                    distances[v] = distances[u] + weight
        
        # Check for negative cycles
        has_negative_cycle = False
        for u, v, weight in edges:
            if distances[u] != float('inf') and distances[u] + weight < distances[v]:
                has_negative_cycle = True
                break
        
        return distances, has_negative_cycle

# Floyd-Warshall Algorithm
class FloydWarshall:
    def all_pairs_shortest_paths(self, matrix):
        """Find shortest paths between all pairs of vertices"""
        n = len(matrix)
        dist = [row[:] for row in matrix]
        
        # Initialize with infinity for no direct path
        for i in range(n):
            for j in range(n):
                if i != j and dist[i][j] == 0:
                    dist[i][j] = float('inf')
        
        # Try all intermediate vertices
        for k in range(n):
            for i in range(n):
                for j in range(n):
                    if dist[i][k] + dist[k][j] < dist[i][j]:
                        dist[i][j] = dist[i][k] + dist[k][j]
        
        return dist
    
    def has_negative_cycle(self, matrix):
        """Check if graph has negative cycle"""
        dist = self.all_pairs_shortest_paths(matrix)
        
        # Check diagonal for negative values
        for i in range(len(dist)):
            if dist[i][i] < 0:
                return True
        
        return False

# Topological Sort
class TopologicalSort:
    def __init__(self):
        self.visited = set()
        self.stack = []
    
    def topological_sort(self, graph):
        """Perform topological sort using DFS"""
        self.visited.clear()
        self.stack = []
        
        for vertex in graph:
            if vertex not in self.visited:
                self._dfs(vertex, graph)
        
        return self.stack[::-1]
    
    def _dfs(self, vertex, graph):
        """DFS helper for topological sort"""
        self.visited.add(vertex)
        
        for neighbor in graph.get(vertex, []):
            if neighbor not in self.visited:
                self._dfs(neighbor, graph)
        
        self.stack.append(vertex)
                                
OCaml
(* Bellman-Ford Algorithm *)
module BellmanFord = struct
  type edge = int * int * int (* u, v, weight *)
  
  let find_shortest_paths edges vertices source =
    let distances = Array.make vertices max_int in
    distances.(source) <- 0;
    
    (* Relax edges V-1 times *)
    for _ = 1 to vertices - 1 do
      List.iter (fun (u, v, weight) ->
        if distances.(u) <> max_int && distances.(u) + weight < distances.(v) then
          distances.(v) <- distances.(u) + weight
      ) edges
    done;
    
    (* Check for negative cycles *)
    let has_negative_cycle = ref false in
    List.iter (fun (u, v, weight) ->
      if distances.(u) <> max_int && distances.(u) + weight < distances.(v) then
        has_negative_cycle := true
    ) edges;
    
    (distances, !has_negative_cycle)
end

(* Floyd-Warshall Algorithm *)
module FloydWarshall = struct
  let all_pairs_shortest_paths matrix =
    let n = Array.length matrix in
    let dist = Array.make_matrix n n 0 in
    
    (* Initialize distance matrix *)
    for i = 0 to n - 1 do
      for j = 0 to n - 1 do
        dist.(i).(j) <- if i = j then 0 
                       else if matrix.(i).(j) = 0 then max_int 
                       else matrix.(i).(j)
      done
    done;
    
    (* Try all intermediate vertices *)
    for k = 0 to n - 1 do
      for i = 0 to n - 1 do
        for j = 0 to n - 1 do
          if dist.(i).(k) <> max_int && dist.(k).(j) <> max_int then
            let new_dist = dist.(i).(k) + dist.(k).(j) in
            if new_dist < dist.(i).(j) then
              dist.(i).(j) <- new_dist
        done
      done
    done;
    
    dist
end

(* Topological Sort *)
module TopologicalSort = struct
  let topological_sort graph =
    let visited = Hashtbl.create 16 in
    let stack = ref [] in
    
    let rec dfs vertex =
      if not (Hashtbl.mem visited vertex) then begin
        Hashtbl.add visited vertex true;
        let neighbors = try Hashtbl.find graph vertex with Not_found -> [] in
        List.iter dfs neighbors;
        stack := vertex :: !stack
      end
    in
    
    Hashtbl.iter (fun vertex _ -> dfs vertex) graph;
    !stack
end
                                
Algorithm Time Complexity Space Complexity Use Case
Bellman-Ford O(VE) O(V) Negative weight edges
Floyd-Warshall O(V³) O(V²) All-pairs shortest paths
Topological Sort O(V + E) O(V) Task scheduling
String Algorithms
Pattern Matching Algorithms
String algorithms optimize pattern searching beyond naive O(nm) approaches. KMP uses failure functions, while Rabin-Karp employs rolling hashes for efficient pattern matching.
🔍 Real-Life Analogy:
Think of finding a word in a book: instead of checking every position character by character, KMP remembers partial matches like knowing "comput" in "computer" means you can skip ahead when you see "com" again.

📝 String Algorithm Applications:

  • Text Editors: Find and replace functionality
  • DNA Sequencing: Pattern matching in genetic data
  • Plagiarism Detection: Finding similar text segments
  • Network Security: Intrusion detection patterns
TypeScript
// KMP (Knuth-Morris-Pratt) Algorithm
class KMPMatcher {
    // Build failure function (longest proper prefix which is also suffix)
    private buildFailureFunction(pattern: string): number[] {
        const m = pattern.length;
        const failure = new Array(m).fill(0);
        let j = 0;
        
        for (let i = 1; i < m; i++) {
            while (j > 0 && pattern[i] !== pattern[j]) {
                j = failure[j - 1];
            }
            
            if (pattern[i] === pattern[j]) {
                j++;
            }
            
            failure[i] = j;
        }
        
        return failure;
    }
    
    // Find all occurrences of pattern in text
    search(text: string, pattern: string): number[] {
        if (pattern.length === 0) return [];
        
        const n = text.length;
        const m = pattern.length;
        const failure = this.buildFailureFunction(pattern);
        const matches: number[] = [];
        let j = 0;
        
        for (let i = 0; i < n; i++) {
            while (j > 0 && text[i] !== pattern[j]) {
                j = failure[j - 1];
            }
            
            if (text[i] === pattern[j]) {
                j++;
            }
            
            if (j === m) {
                matches.push(i - m + 1);
                j = failure[j - 1];
            }
        }
        
        return matches;
    }
}

// Rabin-Karp Algorithm
class RabinKarpMatcher {
    private readonly prime = 101;
    private readonly base = 256;
    
    search(text: string, pattern: string): number[] {
        const n = text.length;
        const m = pattern.length;
        const matches: number[] = [];
        
        if (m > n) return matches;
        
        // Calculate hash values
        let patternHash = 0;
        let textHash = 0;
        let h = 1;
        
        // Calculate h = base^(m-1) % prime
        for (let i = 0; i < m - 1; i++) {
            h = (h * this.base) % this.prime;
        }
        
        // Calculate initial hash values
        for (let i = 0; i < m; i++) {
            patternHash = (this.base * patternHash + pattern.charCodeAt(i)) % this.prime;
            textHash = (this.base * textHash + text.charCodeAt(i)) % this.prime;
        }
        
        // Slide the pattern over text
        for (let i = 0; i <= n - m; i++) {
            // Check if hash values match
            if (patternHash === textHash) {
                // Verify character by character
                let j;
                for (j = 0; j < m; j++) {
                    if (text[i + j] !== pattern[j]) break;
                }
                if (j === m) matches.push(i);
            }
            
            // Calculate hash for next window
            if (i < n - m) {
                textHash = (this.base * (textHash - text.charCodeAt(i) * h) + 
                           text.charCodeAt(i + m)) % this.prime;
                
                // Handle negative hash value
                if (textHash < 0) textHash += this.prime;
            }
        }
        
        return matches;
    }
}

// Example: Find repeated DNA sequences
function findRepeatedDnaSequences(s: string): string[] {
    const seen = new Set();
    const repeated = new Set();
    
    for (let i = 0; i <= s.length - 10; i++) {
        const sequence = s.substring(i, i + 10);
        if (seen.has(sequence)) {
            repeated.add(sequence);
        }
        seen.add(sequence);
    }
    
    return Array.from(repeated);
}
                                
Python
# KMP (Knuth-Morris-Pratt) Algorithm
class KMP:
    def build_failure_function(self, pattern):
        """Build the failure function for KMP"""
        m = len(pattern)
        failure = [0] * m
        j = 0
        
        for i in range(1, m):
            while j > 0 and pattern[i] != pattern[j]:
                j = failure[j - 1]
            
            if pattern[i] == pattern[j]:
                j += 1
            
            failure[i] = j
        
        return failure
    
    def search(self, text, pattern):
        """Find all occurrences of pattern in text"""
        if not pattern:
            return []
        
        n, m = len(text), len(pattern)
        failure = self.build_failure_function(pattern)
        matches = []
        j = 0
        
        for i in range(n):
            while j > 0 and text[i] != pattern[j]:
                j = failure[j - 1]
            
            if text[i] == pattern[j]:
                j += 1
            
            if j == m:
                matches.append(i - m + 1)
                j = failure[j - 1]
        
        return matches

# Rabin-Karp Algorithm
class RabinKarp:
    def __init__(self, base=256, prime=101):
        self.base = base
        self.prime = prime
    
    def search(self, text, pattern):
        """Find all occurrences using rolling hash"""
        n, m = len(text), len(pattern)
        matches = []
        
        if m > n:
            return matches
        
        # Calculate hash values
        pattern_hash = 0
        text_hash = 0
        h = 1
        
        # h = base^(m-1) % prime
        for i in range(m - 1):
            h = (h * self.base) % self.prime
        
        # Calculate initial hash values
        for i in range(m):
            pattern_hash = (self.base * pattern_hash + ord(pattern[i])) % self.prime
            text_hash = (self.base * text_hash + ord(text[i])) % self.prime
        
        # Slide the pattern over text
        for i in range(n - m + 1):
            # Check if hash values match
            if pattern_hash == text_hash:
                # Verify character by character
                if text[i:i+m] == pattern:
                    matches.append(i)
            
            # Calculate hash for next window
            if i < n - m:
                text_hash = (self.base * (text_hash - ord(text[i]) * h) + 
                           ord(text[i + m])) % self.prime
                
                # Handle negative hash value
                if text_hash < 0:
                    text_hash += self.prime
        
        return matches

# Z-Algorithm for pattern matching
def z_algorithm(s):
    """Compute Z array for string matching"""
    n = len(s)
    z = [0] * n
    l = r = 0
    
    for i in range(1, n):
        if i > r:
            l = r = i
            while r < n and s[r - l] == s[r]:
                r += 1
            z[i] = r - l
            r -= 1
        else:
            k = i - l
            if z[k] < r - i + 1:
                z[i] = z[k]
            else:
                l = i
                while r < n and s[r - l] == s[r]:
                    r += 1
                z[i] = r - l
                r -= 1
    
    return z

def z_search(text, pattern):
    """Find all occurrences using Z-algorithm"""
    combined = pattern + "$" + text
    z = z_algorithm(combined)
    matches = []
    
    pattern_len = len(pattern)
    for i in range(pattern_len + 1, len(combined)):
        if z[i] == pattern_len:
            matches.append(i - pattern_len - 1)
    
    return matches

# Example: Longest common substring
def longest_common_substring(s1, s2):
    """Find the longest common substring of two strings"""
    m, n = len(s1), len(s2)
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    max_len = 0
    ending_pos = 0
    
    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] + 1
                if dp[i][j] > max_len:
                    max_len = dp[i][j]
                    ending_pos = i
    
    return s1[ending_pos - max_len:ending_pos]
                                
OCaml
(* KMP Algorithm *)
module KMP = struct
  let build_failure_function pattern =
    let m = String.length pattern in
    let failure = Array.make m 0 in
    let j = ref 0 in
    
    for i = 1 to m - 1 do
      while !j > 0 && pattern.[i] <> pattern.[!j] do
        j := failure.(!j - 1)
      done;
      
      if pattern.[i] = pattern.[!j] then
        incr j;
      
      failure.(i) <- !j
    done;
    
    failure
  
  let search text pattern =
    if String.length pattern = 0 then []
    else
      let n = String.length text in
      let m = String.length pattern in
      let failure = build_failure_function pattern in
      let matches = ref [] in
      let j = ref 0 in
      
      for i = 0 to n - 1 do
        while !j > 0 && text.[i] <> pattern.[!j] do
          j := failure.(!j - 1)
        done;
        
        if text.[i] = pattern.[!j] then
          incr j;
        
        if !j = m then begin
          matches := (i - m + 1) :: !matches;
          j := failure.(!j - 1)
        end
      done;
      
      List.rev !matches
end

(* Rabin-Karp Algorithm *)
module RabinKarp = struct
  let base = 256
  let prime = 101
  
  let hash_string s start len =
    let rec hash i acc =
      if i >= start + len then acc
      else
        let acc' = (base * acc + Char.code s.[i]) mod prime in
        hash (i + 1) acc'
    in
    hash start 0
  
  let search text pattern =
    let n = String.length text in
    let m = String.length pattern in
    let matches = ref [] in
    
    if m <= n then begin
      let pattern_hash = hash_string pattern 0 m in
      let text_hash = ref (hash_string text 0 m) in
      
      (* Calculate h = base^(m-1) mod prime *)
      let h = ref 1 in
      for i = 1 to m - 1 do
        h := (!h * base) mod prime
      done;
      
      for i = 0 to n - m do
        if !text_hash = pattern_hash then begin
          (* Verify character by character *)
          let mismatch = ref false in
          for j = 0 to m - 1 do
            if text.[i + j] <> pattern.[j] then
              mismatch := true
          done;
          if not !mismatch then
            matches := i :: !matches
        end;
        
        (* Calculate hash for next window *)
        if i < n - m then begin
          text_hash := (base * (!text_hash - Char.code text.[i] * !h) + 
                       Char.code text.[i + m]) mod prime;
          if !text_hash < 0 then
            text_hash := !text_hash + prime
        end
      done
    end;
    
    List.rev !matches
end

(* Z-Algorithm *)
module ZAlgorithm = struct
  let compute_z_array s =
    let n = String.length s in
    let z = Array.make n 0 in
    let l = ref 0 in
    let r = ref 0 in
    
    for i = 1 to n - 1 do
      if i > !r then begin
        l := i;
        r := i;
        while !r < n && s.[!r - !l] = s.[!r] do
          incr r
        done;
        z.(i) <- !r - !l;
        decr r
      end else begin
        let k = i - !l in
        if z.(k) < !r - i + 1 then
          z.(i) <- z.(k)
        else begin
          l := i;
          while !r < n && s.[!r - !l] = s.[!r] do
            incr r
          done;
          z.(i) <- !r - !l;
          decr r
        end
      end
    done;
    
    z
  
  let search text pattern =
    let combined = pattern ^ "$" ^ text in
    let z = compute_z_array combined in
    let pattern_len = String.length pattern in
    let matches = ref [] in
    
    for i = pattern_len + 1 to String.length combined - 1 do
      if z.(i) = pattern_len then
        matches := (i - pattern_len - 1) :: !matches
    done;
    
    List.rev !matches
end
                                
Algorithm Preprocessing Searching Space Best For
KMP O(m) O(n) O(m) No backtracking needed
Rabin-Karp O(m) O(n) average O(1) Multiple pattern search
Z-Algorithm O(m + n) O(m + n) O(m + n) Pattern variations
Advanced Sorting
Non-Comparison Based Sorting
While comparison-based sorts have a lower bound of O(n log n), non-comparison sorts like Counting Sort and Radix Sort can achieve O(n) time complexity under specific conditions.
📬 Real-Life Analogy:
Think of sorting mail by ZIP code: instead of comparing each letter with others, you create bins for each ZIP code and distribute letters directly into appropriate bins.

🎯 When to Use:

  • Counting Sort: Small range of integers
  • Radix Sort: Fixed-width integers or strings
  • Bucket Sort: Uniformly distributed data
  • External Sort: Data too large for memory
TypeScript
// Counting Sort
function countingSort(arr: number[], maxValue: number): number[] {
    const count = new Array(maxValue + 1).fill(0);
    const output = new Array(arr.length);
    
    // Count occurrences
    for (const num of arr) {
        count[num]++;
    }
    
    // Transform to cumulative count
    for (let i = 1; i <= maxValue; i++) {
        count[i] += count[i - 1];
    }
    
    // Build output array (stable sort)
    for (let i = arr.length - 1; i >= 0; i--) {
        output[count[arr[i]] - 1] = arr[i];
        count[arr[i]]--;
    }
    
    return output;
}

// Radix Sort
function radixSort(arr: number[]): number[] {
    if (arr.length === 0) return arr;
    
    const getMax = (arr: number[]) => Math.max(...arr);
    const getDigit = (num: number, place: number) => 
        Math.floor(Math.abs(num) / Math.pow(10, place)) % 10;
    
    const maxNum = getMax(arr);
    const maxDigits = Math.floor(Math.log10(maxNum)) + 1;
    
    let result = [...arr];
    
    for (let k = 0; k < maxDigits; k++) {
        const buckets: number[][] = Array.from({ length: 10 }, () => []);
        
        for (const num of result) {
            const digit = getDigit(num, k);
            buckets[digit].push(num);
        }
        
        result = buckets.flat();
    }
    
    return result;
}

// Bucket Sort
function bucketSort(arr: number[]): number[] {
    if (arr.length === 0) return arr;
    
    const n = arr.length;
    const buckets: number[][] = Array.from({ length: n }, () => []);
    
    // Find range
    const minVal = Math.min(...arr);
    const maxVal = Math.max(...arr);
    const range = maxVal - minVal;
    
    // Distribute to buckets
    for (const num of arr) {
        const index = Math.floor(((num - minVal) / range) * (n - 1));
        buckets[index].push(num);
    }
    
    // Sort individual buckets and concatenate
    const result: number[] = [];
    for (const bucket of buckets) {
        bucket.sort((a, b) => a - b);
        result.push(...bucket);
    }
    
    return result;
}

// External Merge Sort for large datasets
class ExternalSort {
    private chunkSize: number;
    
    constructor(chunkSize: number = 1000000) {
        this.chunkSize = chunkSize;
    }
    
    // Simulate sorting large file
    sort(data: number[]): number[] {
        const chunks: number[][] = [];
        
        // Step 1: Sort chunks that fit in memory
        for (let i = 0; i < data.length; i += this.chunkSize) {
            const chunk = data.slice(i, i + this.chunkSize);
            chunk.sort((a, b) => a - b);
            chunks.push(chunk);
        }
        
        // Step 2: K-way merge
        return this.kWayMerge(chunks);
    }
    
    private kWayMerge(chunks: number[][]): number[] {
        const result: number[] = [];
        const indices = new Array(chunks.length).fill(0);
        
        // Min heap for efficient minimum finding
        const heap: [number, number][] = []; // [value, chunk index]
        
        // Initialize heap with first element from each chunk
        for (let i = 0; i < chunks.length; i++) {
            if (chunks[i].length > 0) {
                heap.push([chunks[i][0], i]);
            }
        }
        
        // Min heapify
        heap.sort((a, b) => a[0] - b[0]);
        
        while (heap.length > 0) {
            const [value, chunkIndex] = heap.shift()!;
            result.push(value);
            indices[chunkIndex]++;
            
            if (indices[chunkIndex] < chunks[chunkIndex].length) {
                heap.push([chunks[chunkIndex][indices[chunkIndex]], chunkIndex]);
                heap.sort((a, b) => a[0] - b[0]);
            }
        }
        
        return result;
    }
}
                                
Python
# Counting Sort
def counting_sort(arr, max_value):
    """Sort array using counting sort"""
    count = [0] * (max_value + 1)
    output = [0] * len(arr)
    
    # Count occurrences
    for num in arr:
        count[num] += 1
    
    # Transform to cumulative count
    for i in range(1, max_value + 1):
        count[i] += count[i - 1]
    
    # Build output array (stable sort)
    for i in range(len(arr) - 1, -1, -1):
        output[count[arr[i]] - 1] = arr[i]
        count[arr[i]] -= 1
    
    return output

# Radix Sort
def radix_sort(arr):
    """Sort array using radix sort"""
    if not arr:
        return arr
    
    def get_digit(num, place):
        return (abs(num) // (10 ** place)) % 10
    
    max_num = max(arr)
    max_digits = len(str(max_num))
    
    result = arr[:]
    
    for digit_place in range(max_digits):
        buckets = [[] for _ in range(10)]
        
        for num in result:
            digit = get_digit(num, digit_place)
            buckets[digit].append(num)
        
        result = []
        for bucket in buckets:
            result.extend(bucket)
    
    return result

# Bucket Sort
def bucket_sort(arr):
    """Sort array using bucket sort"""
    if not arr:
        return arr
    
    n = len(arr)
    buckets = [[] for _ in range(n)]
    
    # Find range
    min_val = min(arr)
    max_val = max(arr)
    range_val = max_val - min_val
    
    if range_val == 0:
        return arr
    
    # Distribute to buckets
    for num in arr:
        index = int(((num - min_val) / range_val) * (n - 1))
        buckets[index].append(num)
    
    # Sort individual buckets and concatenate
    result = []
    for bucket in buckets:
        bucket.sort()
        result.extend(bucket)
    
    return result

# External Sort using heapq
import heapq

class ExternalSort:
    def __init__(self, chunk_size=1000000):
        self.chunk_size = chunk_size
    
    def sort(self, data):
        """Sort large dataset using external merge sort"""
        chunks = []
        
        # Step 1: Sort chunks that fit in memory
        for i in range(0, len(data), self.chunk_size):
            chunk = sorted(data[i:i + self.chunk_size])
            chunks.append(chunk)
        
        # Step 2: K-way merge using heap
        return self.k_way_merge(chunks)
    
    def k_way_merge(self, chunks):
        """Merge sorted chunks using a min heap"""
        result = []
        heap = []
        
        # Initialize heap with iterators
        for i, chunk in enumerate(chunks):
            if chunk:
                # Push (value, chunk_index, element_index)
                heapq.heappush(heap, (chunk[0], i, 0))
        
        while heap:
            value, chunk_idx, elem_idx = heapq.heappop(heap)
            result.append(value)
            
            # Add next element from same chunk if available
            if elem_idx + 1 < len(chunks[chunk_idx]):
                next_value = chunks[chunk_idx][elem_idx + 1]
                heapq.heappush(heap, (next_value, chunk_idx, elem_idx + 1))
        
        return result
                                
OCaml
(* Counting Sort *)
let counting_sort arr max_value =
  let n = Array.length arr in
  let count = Array.make (max_value + 1) 0 in
  let output = Array.make n 0 in
  
  (* Count occurrences *)
  Array.iter (fun x -> count.(x) <- count.(x) + 1) arr;
  
  (* Transform to cumulative count *)
  for i = 1 to max_value do
    count.(i) <- count.(i) + count.(i - 1)
  done;
  
  (* Build output array *)
  for i = n - 1 downto 0 do
    let value = arr.(i) in
    output.(count.(value) - 1) <- value;
    count.(value) <- count.(value) - 1
  done;
  
  output

(* Radix Sort *)
let radix_sort arr =
  if Array.length arr = 0 then arr
  else
    let get_digit num place =
      (abs num / int_of_float (10.0 ** float_of_int place)) mod 10
    in
    
    let max_num = Array.fold_left max arr.(0) arr in
    let max_digits = String.length (string_of_int max_num) in
    
    let result = ref (Array.copy arr) in
    
    for digit_place = 0 to max_digits - 1 do
      let buckets = Array.init 10 (fun _ -> ref []) in
      
      (* Distribute to buckets *)
      Array.iter (fun num ->
        let digit = get_digit num digit_place in
        buckets.(digit) := num :: !(buckets.(digit))
      ) !result;
      
      (* Collect from buckets *)
      let collected = ref [] in
      Array.iter (fun bucket ->
        collected := !collected @ (List.rev !bucket)
      ) buckets;
      
      result := Array.of_list !collected
    done;
    
    !result
                                
Algorithm Time Complexity Space Complexity Stable Constraints
Counting Sort O(n + k) O(k) Yes k = range of input
Radix Sort O(d × n) O(n) Yes d = number of digits
Bucket Sort O(n) average O(n) Yes Uniform distribution
External Sort O(n log n) O(M) Yes M = memory limit
Divide & Conquer Algorithms
Advanced Divide & Conquer Techniques
Divide and conquer algorithms solve problems by breaking them into smaller subproblems, solving them independently, and combining results. Advanced techniques optimize the division and combination steps.
🧩 Real-Life Analogy:
Think of assembling a large jigsaw puzzle: divide it into sections (sky, ground, etc.), solve each section independently with different people, then combine the completed sections.

🎯 Key Divide & Conquer Algorithms:

  • Closest Pair of Points: Geometric divide and conquer
  • Fast Fourier Transform: Signal processing
  • Karatsuba Multiplication: Large number multiplication
  • Strassen's Matrix Multiplication: Efficient matrix operations
TypeScript
// Closest Pair of Points
interface Point {
    x: number;
    y: number;
}

class ClosestPair {
    private distance(p1: Point, p2: Point): number {
        return Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2);
    }
    
    private bruteForce(points: Point[]): number {
        let minDist = Infinity;
        for (let i = 0; i < points.length; i++) {
            for (let j = i + 1; j < points.length; j++) {
                minDist = Math.min(minDist, this.distance(points[i], points[j]));
            }
        }
        return minDist;
    }
    
    private stripClosest(strip: Point[], d: number): number {
        let minDist = d;
        strip.sort((a, b) => a.y - b.y);
        
        for (let i = 0; i < strip.length; i++) {
            for (let j = i + 1; j < strip.length && (strip[j].y - strip[i].y) < minDist; j++) {
                minDist = Math.min(minDist, this.distance(strip[i], strip[j]));
            }
        }
        
        return minDist;
    }
    
    private closestPairRec(px: Point[], py: Point[]): number {
        const n = px.length;
        
        if (n <= 3) {
            return this.bruteForce(px);
        }
        
        const mid = Math.floor(n / 2);
        const midpoint = px[mid];
        
        const pyl = py.filter(point => point.x <= midpoint.x);
        const pyr = py.filter(point => point.x > midpoint.x);
        
        const dl = this.closestPairRec(px.slice(0, mid), pyl);
        const dr = this.closestPairRec(px.slice(mid), pyr);
        
        const d = Math.min(dl, dr);
        
        const strip = py.filter(point => Math.abs(point.x - midpoint.x) < d);
        
        return Math.min(d, this.stripClosest(strip, d));
    }
    
    findClosestPair(points: Point[]): number {
        const px = [...points].sort((a, b) => a.x - b.x);
        const py = [...points].sort((a, b) => a.y - b.y);
        
        return this.closestPairRec(px, py);
    }
}

// Karatsuba Multiplication
class KaratsubaMultiplication {
    multiply(x: bigint, y: bigint): bigint {
        if (x < 10n || y < 10n) {
            return x * y;
        }
        
        const xStr = x.toString();
        const yStr = y.toString();
        const n = Math.max(xStr.length, yStr.length);
        const half = Math.floor(n / 2);
        
        const multiplier = 10n ** BigInt(half);
        
        const high1 = x / multiplier;
        const low1 = x % multiplier;
        const high2 = y / multiplier;
        const low2 = y % multiplier;
        
        const z0 = this.multiply(low1, low2);
        const z1 = this.multiply(low1 + high1, low2 + high2);
        const z2 = this.multiply(high1, high2);
        
        return z2 * (multiplier ** 2n) + (z1 - z2 - z0) * multiplier + z0;
    }
}

// Matrix Multiplication (Strassen's Algorithm)
class StrassenMatrix {
    multiply(A: number[][], B: number[][]): number[][] {
        const n = A.length;
        
        if (n <= 2) {
            return this.standardMultiply(A, B);
        }
        
        const half = Math.floor(n / 2);
        
        // Divide matrices into quadrants
        const A11 = this.getSubMatrix(A, 0, 0, half);
        const A12 = this.getSubMatrix(A, 0, half, half);
        const A21 = this.getSubMatrix(A, half, 0, half);
        const A22 = this.getSubMatrix(A, half, half, half);
        
        const B11 = this.getSubMatrix(B, 0, 0, half);
        const B12 = this.getSubMatrix(B, 0, half, half);
        const B21 = this.getSubMatrix(B, half, 0, half);
        const B22 = this.getSubMatrix(B, half, half, half);
        
        // Calculate 7 products (Strassen's method)
        const M1 = this.multiply(this.add(A11, A22), this.add(B11, B22));
        const M2 = this.multiply(this.add(A21, A22), B11);
        const M3 = this.multiply(A11, this.subtract(B12, B22));
        const M4 = this.multiply(A22, this.subtract(B21, B11));
        const M5 = this.multiply(this.add(A11, A12), B22);
        const M6 = this.multiply(this.subtract(A21, A11), this.add(B11, B12));
        const M7 = this.multiply(this.subtract(A12, A22), this.add(B21, B22));
        
        // Calculate result quadrants
        const C11 = this.add(this.subtract(this.add(M1, M4), M5), M7);
        const C12 = this.add(M3, M5);
        const C21 = this.add(M2, M4);
        const C22 = this.add(this.subtract(this.add(M1, M3), M2), M6);
        
        // Combine quadrants
        return this.combineMatrices(C11, C12, C21, C22);
    }
    
    private standardMultiply(A: number[][], B: number[][]): number[][] {
        const n = A.length;
        const result = Array(n).fill(0).map(() => Array(n).fill(0));
        
        for (let i = 0; i < n; i++) {
            for (let j = 0; j < n; j++) {
                for (let k = 0; k < n; k++) {
                    result[i][j] += A[i][k] * B[k][j];
                }
            }
        }
        
        return result;
    }
    
    private getSubMatrix(matrix: number[][], startRow: number, startCol: number, size: number): number[][] {
        const result = Array(size).fill(0).map(() => Array(size).fill(0));
        for (let i = 0; i < size; i++) {
            for (let j = 0; j < size; j++) {
                result[i][j] = matrix[startRow + i][startCol + j];
            }
        }
        return result;
    }
    
    private add(A: number[][], B: number[][]): number[][] {
        const n = A.length;
        const result = Array(n).fill(0).map(() => Array(n).fill(0));
        for (let i = 0; i < n; i++) {
            for (let j = 0; j < n; j++) {
                result[i][j] = A[i][j] + B[i][j];
            }
        }
        return result;
    }
    
    private subtract(A: number[][], B: number[][]): number[][] {
        const n = A.length;
        const result = Array(n).fill(0).map(() => Array(n).fill(0));
        for (let i = 0; i < n; i++) {
            for (let j = 0; j < n; j++) {
                result[i][j] = A[i][j] - B[i][j];
            }
        }
        return result;
    }
    
    private combineMatrices(C11: number[][], C12: number[][], C21: number[][], C22: number[][]): number[][] {
        const half = C11.length;
        const n = half * 2;
        const result = Array(n).fill(0).map(() => Array(n).fill(0));
        
        for (let i = 0; i < half; i++) {
            for (let j = 0; j < half; j++) {
                result[i][j] = C11[i][j];
                result[i][j + half] = C12[i][j];
                result[i + half][j] = C21[i][j];
                result[i + half][j + half] = C22[i][j];
            }
        }
        
        return result;
    }
}
                                
Python
# Closest Pair of Points
import math
from typing import List, Tuple

class Point:
    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

class ClosestPair:
    def distance(self, p1: Point, p2: Point) -> float:
        return math.sqrt((p1.x - p2.x)**2 + (p1.y - p2.y)**2)
    
    def brute_force(self, points: List[Point]) -> float:
        min_dist = float('inf')
        n = len(points)
        for i in range(n):
            for j in range(i + 1, n):
                min_dist = min(min_dist, self.distance(points[i], points[j]))
        return min_dist
    
    def strip_closest(self, strip: List[Point], d: float) -> float:
        min_dist = d
        strip.sort(key=lambda p: p.y)
        
        for i in range(len(strip)):
            j = i + 1
            while j < len(strip) and (strip[j].y - strip[i].y) < min_dist:
                min_dist = min(min_dist, self.distance(strip[i], strip[j]))
                j += 1
        
        return min_dist
    
    def closest_pair_rec(self, px: List[Point], py: List[Point]) -> float:
        n = len(px)
        
        if n <= 3:
            return self.brute_force(px)
        
        mid = n // 2
        midpoint = px[mid]
        
        pyl = [point for point in py if point.x <= midpoint.x]
        pyr = [point for point in py if point.x > midpoint.x]
        
        dl = self.closest_pair_rec(px[:mid], pyl)
        dr = self.closest_pair_rec(px[mid:], pyr)
        
        d = min(dl, dr)
        
        strip = [point for point in py if abs(point.x - midpoint.x) < d]
        
        return min(d, self.strip_closest(strip, d))
    
    def find_closest_pair(self, points: List[Point]) -> float:
        px = sorted(points, key=lambda p: p.x)
        py = sorted(points, key=lambda p: p.y)
        
        return self.closest_pair_rec(px, py)

# Karatsuba Multiplication
class KaratsubaMultiplication:
    def multiply(self, x: int, y: int) -> int:
        if x < 10 or y < 10:
            return x * y
        
        n = max(len(str(x)), len(str(y)))
        half = n // 2
        
        multiplier = 10 ** half
        
        high1, low1 = divmod(x, multiplier)
        high2, low2 = divmod(y, multiplier)
        
        z0 = self.multiply(low1, low2)
        z1 = self.multiply(low1 + high1, low2 + high2)
        z2 = self.multiply(high1, high2)
        
        return z2 * (multiplier ** 2) + (z1 - z2 - z0) * multiplier + z0

# Matrix Multiplication (Strassen's Algorithm)
class StrassenMatrix:
    def multiply(self, A: List[List[int]], B: List[List[int]]) -> List[List[int]]:
        n = len(A)
        
        if n <= 2:
            return self.standard_multiply(A, B)
        
        half = n // 2
        
        # Divide matrices into quadrants
        A11 = self.get_sub_matrix(A, 0, 0, half)
        A12 = self.get_sub_matrix(A, 0, half, half)
        A21 = self.get_sub_matrix(A, half, 0, half)
        A22 = self.get_sub_matrix(A, half, half, half)
        
        B11 = self.get_sub_matrix(B, 0, 0, half)
        B12 = self.get_sub_matrix(B, 0, half, half)
        B21 = self.get_sub_matrix(B, half, 0, half)
        B22 = self.get_sub_matrix(B, half, half, half)
        
        # Calculate 7 products (Strassen's method)
        M1 = self.multiply(self.add(A11, A22), self.add(B11, B22))
        M2 = self.multiply(self.add(A21, A22), B11)
        M3 = self.multiply(A11, self.subtract(B12, B22))
        M4 = self.multiply(A22, self.subtract(B21, B11))
        M5 = self.multiply(self.add(A11, A12), B22)
        M6 = self.multiply(self.subtract(A21, A11), self.add(B11, B12))
        M7 = self.multiply(self.subtract(A12, A22), self.add(B21, B22))
        
        # Calculate result quadrants
        C11 = self.add(self.subtract(self.add(M1, M4), M5), M7)
        C12 = self.add(M3, M5)
        C21 = self.add(M2, M4)
        C22 = self.add(self.subtract(self.add(M1, M3), M2), M6)
        
        # Combine quadrants
        return self.combine_matrices(C11, C12, C21, C22)
    
    def standard_multiply(self, A: List[List[int]], B: List[List[int]]) -> List[List[int]]:
        n = len(A)
        result = [[0] * n for _ in range(n)]
        
        for i in range(n):
            for j in range(n):
                for k in range(n):
                    result[i][j] += A[i][k] * B[k][j]
        
        return result
    
    def get_sub_matrix(self, matrix: List[List[int]], start_row: int, start_col: int, size: int) -> List[List[int]]:
        return [[matrix[start_row + i][start_col + j] for j in range(size)] for i in range(size)]
    
    def add(self, A: List[List[int]], B: List[List[int]]) -> List[List[int]]:
        n = len(A)
        return [[A[i][j] + B[i][j] for j in range(n)] for i in range(n)]
    
    def subtract(self, A: List[List[int]], B: List[List[int]]) -> List[List[int]]:
        n = len(A)
        return [[A[i][j] - B[i][j] for j in range(n)] for i in range(n)]
    
    def combine_matrices(self, C11, C12, C21, C22):
        half = len(C11)
        n = half * 2
        result = [[0] * n for _ in range(n)]
        
        for i in range(half):
            for j in range(half):
                result[i][j] = C11[i][j]
                result[i][j + half] = C12[i][j]
                result[i + half][j] = C21[i][j]
                result[i + half][j + half] = C22[i][j]
        
        return result
                                
OCaml
(* Closest Pair of Points *)
module ClosestPair = struct
  type point = { x: float; y: float }
  
  let distance p1 p2 =
    sqrt ((p1.x -. p2.x) ** 2.0 +. (p1.y -. p2.y) ** 2.0)
  
  let brute_force points =
    let rec loop i j min_dist =
      if i >= Array.length points then min_dist
      else if j >= Array.length points then loop (i + 1) (i + 2) min_dist
      else
        let dist = distance points.(i) points.(j) in
        loop i (j + 1) (min min_dist dist)
    in
    if Array.length points < 2 then infinity
    else loop 0 1 infinity
  
  let strip_closest strip d =
    Array.sort (fun p1 p2 -> compare p1.y p2.y) strip;
    let min_dist = ref d in
    for i = 0 to Array.length strip - 1 do
      let j = ref (i + 1) in
      while !j < Array.length strip && (strip.(!j).y -. strip.(i).y) < !min_dist do
        min_dist := min !min_dist (distance strip.(i) strip.(!j));
        incr j
      done
    done;
    !min_dist
  
  let rec closest_pair_rec px py =
    let n = Array.length px in
    if n <= 3 then brute_force px
    else
      let mid = n / 2 in
      let midpoint = px.(mid) in
      
      let pyl = Array.of_list (List.filter (fun p -> p.x <= midpoint.x) (Array.to_list py)) in
      let pyr = Array.of_list (List.filter (fun p -> p.x > midpoint.x) (Array.to_list py)) in
      
      let dl = closest_pair_rec (Array.sub px 0 mid) pyl in
      let dr = closest_pair_rec (Array.sub px mid (n - mid)) pyr in
      
      let d = min dl dr in
      
      let strip = Array.of_list (List.filter (fun p -> abs_float (p.x -. midpoint.x) < d) (Array.to_list py)) in
      
      min d (strip_closest strip d)
  
  let find_closest_pair points =
    let px = Array.copy points in
    let py = Array.copy points in
    Array.sort (fun p1 p2 -> compare p1.x p2.x) px;
    Array.sort (fun p1 p2 -> compare p1.y p2.y) py;
    closest_pair_rec px py
end

(* Karatsuba Multiplication *)
module Karatsuba = struct
  let rec multiply x y =
    if x < 10 || y < 10 then x * y
    else
      let x_str = string_of_int x in
      let y_str = string_of_int y in
      let n = max (String.length x_str) (String.length y_str) in
      let half = n / 2 in
      let multiplier = int_of_float (10.0 ** float_of_int half) in
      
      let high1 = x / multiplier in
      let low1 = x mod multiplier in
      let high2 = y / multiplier in
      let low2 = y mod multiplier in
      
      let z0 = multiply low1 low2 in
      let z1 = multiply (low1 + high1) (low2 + high2) in
      let z2 = multiply high1 high2 in
      
      z2 * (multiplier * multiplier) + (z1 - z2 - z0) * multiplier + z0
end
                                
Algorithm Time Complexity Traditional Improvement
Closest Pair O(n log n) O(n²) Divide plane by x-coordinate
Karatsuba O(n^1.585) O(n²) 3 multiplications instead of 4
Strassen's O(n^2.807) O(n³) 7 multiplications instead of 8
FFT O(n log n) O(n²) Divide by even/odd indices