Complex Techniques

Level 4: Advanced Mastery - Master sophisticated algorithmic techniques and optimization methods

Complex Techniques
Advanced Backtracking with Optimization
Optimized Backtracking
Advanced backtracking involves sophisticated pruning techniques, memoization, and optimization strategies to reduce the search space dramatically.
🌳 Real-Life Analogy:
Think of exploring a maze with smart techniques: instead of blindly trying every path, you use landmarks, keep notes of dead ends, and employ strategic shortcuts to find solutions faster.
TypeScript
// N-Queens with advanced optimizations
class NQueensOptimized {
    private n: number;
    private solutions: number[][];
    private cols: Set;
    private diag1: Set; // row + col
    private diag2: Set; // row - col
    
    constructor(n: number) {
        this.n = n;
        this.solutions = [];
        this.cols = new Set();
        this.diag1 = new Set();
        this.diag2 = new Set();
    }
    
    solveNQueens(): number[][] {
        const board: number[] = new Array(this.n).fill(-1);
        this.backtrack(board, 0);
        return this.solutions;
    }
    
    private backtrack(board: number[], row: number): void {
        if (row === this.n) {
            this.solutions.push([...board]);
            return;
        }
        
        for (let col = 0; col < this.n; col++) {
            if (this.isSafe(row, col)) {
                // Place queen
                board[row] = col;
                this.cols.add(col);
                this.diag1.add(row + col);
                this.diag2.add(row - col);
                
                this.backtrack(board, row + 1);
                
                // Remove queen (backtrack)
                this.cols.delete(col);
                this.diag1.delete(row + col);
                this.diag2.delete(row - col);
                board[row] = -1;
            }
        }
    }
    
    private isSafe(row: number, col: number): boolean {
        return !this.cols.has(col) && 
               !this.diag1.has(row + col) && 
               !this.diag2.has(row - col);
    }
}

// Sudoku Solver with constraint propagation
class SudokuSolver {
    private board: number[][];
    private rows: Set[];
    private cols: Set[];
    private boxes: Set[];
    
    constructor(board: number[][]) {
        this.board = board.map(row => [...row]);
        this.rows = Array.from({length: 9}, () => new Set());
        this.cols = Array.from({length: 9}, () => new Set());
        this.boxes = Array.from({length: 9}, () => new Set());
        
        // Initialize constraints
        for (let r = 0; r < 9; r++) {
            for (let c = 0; c < 9; c++) {
                if (this.board[r][c] !== 0) {
                    const num = this.board[r][c];
                    const boxIndex = Math.floor(r / 3) * 3 + Math.floor(c / 3);
                    this.rows[r].add(num);
                    this.cols[c].add(num);
                    this.boxes[boxIndex].add(num);
                }
            }
        }
    }
    
    solve(): boolean {
        const emptyCell = this.findBestEmptyCell();
        if (!emptyCell) return true; // Solved
        
        const [row, col] = emptyCell;
        const possibleValues = this.getPossibleValues(row, col);
        
        for (const num of possibleValues) {
            if (this.isValid(row, col, num)) {
                this.placeNumber(row, col, num);
                
                if (this.solve()) return true;
                
                this.removeNumber(row, col, num);
            }
        }
        
        return false;
    }
    
    private findBestEmptyCell(): [number, number] | null {
        let minOptions = 10;
        let bestCell: [number, number] | null = null;
        
        for (let r = 0; r < 9; r++) {
            for (let c = 0; c < 9; c++) {
                if (this.board[r][c] === 0) {
                    const options = this.getPossibleValues(r, c).length;
                    if (options < minOptions) {
                        minOptions = options;
                        bestCell = [r, c];
                        if (minOptions === 1) return bestCell; // Early termination
                    }
                }
            }
        }
        
        return bestCell;
    }
    
    private getPossibleValues(row: number, col: number): number[] {
        const possible: number[] = [];
        for (let num = 1; num <= 9; num++) {
            if (this.isValid(row, col, num)) {
                possible.push(num);
            }
        }
        return possible;
    }
    
    private isValid(row: number, col: number, num: number): boolean {
        const boxIndex = Math.floor(row / 3) * 3 + Math.floor(col / 3);
        return !this.rows[row].has(num) &&
               !this.cols[col].has(num) &&
               !this.boxes[boxIndex].has(num);
    }
    
    private placeNumber(row: number, col: number, num: number): void {
        this.board[row][col] = num;
        const boxIndex = Math.floor(row / 3) * 3 + Math.floor(col / 3);
        this.rows[row].add(num);
        this.cols[col].add(num);
        this.boxes[boxIndex].add(num);
    }
    
    private removeNumber(row: number, col: number, num: number): void {
        this.board[row][col] = 0;
        const boxIndex = Math.floor(row / 3) * 3 + Math.floor(col / 3);
        this.rows[row].delete(num);
        this.cols[col].delete(num);
        this.boxes[boxIndex].delete(num);
    }
}

// Word Break with memoization
class WordBreak {
    private memo: Map = new Map();
    
    wordBreak(s: string, wordDict: string[]): boolean {
        if (this.memo.has(s)) return this.memo.get(s)!;
        
        if (s.length === 0) return true;
        
        for (const word of wordDict) {
            if (s.startsWith(word)) {
                const remaining = s.substring(word.length);
                if (this.wordBreak(remaining, wordDict)) {
                    this.memo.set(s, true);
                    return true;
                }
            }
        }
        
        this.memo.set(s, false);
        return false;
    }
    
    // Word Break II - return all possible sentences
    wordBreakII(s: string, wordDict: string[]): string[] {
        const memoSentences: Map = new Map();
        
        const backtrack = (remaining: string): string[] => {
            if (memoSentences.has(remaining)) {
                return memoSentences.get(remaining)!;
            }
            
            if (remaining.length === 0) return [''];
            
            const result: string[] = [];
            
            for (const word of wordDict) {
                if (remaining.startsWith(word)) {
                    const suffix = remaining.substring(word.length);
                    const suffixSentences = backtrack(suffix);
                    
                    for (const sentence of suffixSentences) {
                        const newSentence = sentence.length === 0 ? word : word + ' ' + sentence;
                        result.push(newSentence);
                    }
                }
            }
            
            memoSentences.set(remaining, result);
            return result;
        };
        
        return backtrack(s);
    }
}
                                
Python
from typing import List, Set, Dict, Optional, Tuple
from functools import lru_cache

class NQueensOptimized:
    def __init__(self, n: int):
        self.n = n
        self.solutions = []
        self.cols = set()
        self.diag1 = set()  # row + col
        self.diag2 = set()  # row - col
    
    def solve_n_queens(self) -> List[List[int]]:
        """Solve N-Queens with optimized backtracking"""
        board = [-1] * self.n
        self._backtrack(board, 0)
        return self.solutions
    
    def _backtrack(self, board: List[int], row: int) -> None:
        if row == self.n:
            self.solutions.append(board[:])
            return
        
        for col in range(self.n):
            if self._is_safe(row, col):
                # Place queen
                board[row] = col
                self.cols.add(col)
                self.diag1.add(row + col)
                self.diag2.add(row - col)
                
                self._backtrack(board, row + 1)
                
                # Remove queen (backtrack)
                self.cols.remove(col)
                self.diag1.remove(row + col)
                self.diag2.remove(row - col)
                board[row] = -1
    
    def _is_safe(self, row: int, col: int) -> bool:
        return (col not in self.cols and 
                (row + col) not in self.diag1 and 
                (row - col) not in self.diag2)

class SudokuSolver:
    def __init__(self, board: List[List[int]]):
        self.board = [row[:] for row in board]
        self.rows = [set() for _ in range(9)]
        self.cols = [set() for _ in range(9)]
        self.boxes = [set() for _ in range(9)]
        
        # Initialize constraints
        for r in range(9):
            for c in range(9):
                if self.board[r][c] != 0:
                    num = self.board[r][c]
                    box_index = (r // 3) * 3 + (c // 3)
                    self.rows[r].add(num)
                    self.cols[c].add(num)
                    self.boxes[box_index].add(num)
    
    def solve(self) -> bool:
        """Solve Sudoku with constraint propagation"""
        empty_cell = self._find_best_empty_cell()
        if not empty_cell:
            return True  # Solved
        
        row, col = empty_cell
        possible_values = self._get_possible_values(row, col)
        
        for num in possible_values:
            if self._is_valid(row, col, num):
                self._place_number(row, col, num)
                
                if self.solve():
                    return True
                
                self._remove_number(row, col, num)
        
        return False
    
    def _find_best_empty_cell(self) -> Optional[Tuple[int, int]]:
        """Find empty cell with minimum possible values (MRV heuristic)"""
        min_options = 10
        best_cell = None
        
        for r in range(9):
            for c in range(9):
                if self.board[r][c] == 0:
                    options = len(self._get_possible_values(r, c))
                    if options < min_options:
                        min_options = options
                        best_cell = (r, c)
                        if min_options == 1:
                            return best_cell  # Early termination
        
        return best_cell
    
    def _get_possible_values(self, row: int, col: int) -> List[int]:
        possible = []
        for num in range(1, 10):
            if self._is_valid(row, col, num):
                possible.append(num)
        return possible
    
    def _is_valid(self, row: int, col: int, num: int) -> bool:
        box_index = (row // 3) * 3 + (col // 3)
        return (num not in self.rows[row] and
                num not in self.cols[col] and
                num not in self.boxes[box_index])
    
    def _place_number(self, row: int, col: int, num: int) -> None:
        self.board[row][col] = num
        box_index = (row // 3) * 3 + (col // 3)
        self.rows[row].add(num)
        self.cols[col].add(num)
        self.boxes[box_index].add(num)
    
    def _remove_number(self, row: int, col: int, num: int) -> None:
        self.board[row][col] = 0
        box_index = (row // 3) * 3 + (col // 3)
        self.rows[row].remove(num)
        self.cols[col].remove(num)
        self.boxes[box_index].remove(num)

class WordBreak:
    def __init__(self):
        self.memo = {}
    
    def word_break(self, s: str, word_dict: List[str]) -> bool:
        """Word Break with memoization"""
        if s in self.memo:
            return self.memo[s]
        
        if not s:
            return True
        
        for word in word_dict:
            if s.startswith(word):
                if self.word_break(s[len(word):], word_dict):
                    self.memo[s] = True
                    return True
        
        self.memo[s] = False
        return False
    
    def word_break_ii(self, s: str, word_dict: List[str]) -> List[str]:
        """Word Break II - return all possible sentences"""
        memo_sentences = {}
        
        def backtrack(remaining: str) -> List[str]:
            if remaining in memo_sentences:
                return memo_sentences[remaining]
            
            if not remaining:
                return ['']
            
            result = []
            
            for word in word_dict:
                if remaining.startswith(word):
                    suffix = remaining[len(word):]
                    suffix_sentences = backtrack(suffix)
                    
                    for sentence in suffix_sentences:
                        new_sentence = word if not sentence else word + ' ' + sentence
                        result.append(new_sentence)
            
            memo_sentences[remaining] = result
            return result
        
        return backtrack(s)

# Combination Sum with pruning
def combination_sum_optimized(candidates: List[int], target: int) -> List[List[int]]:
    """Combination Sum with advanced pruning"""
    candidates.sort()  # Sort for pruning
    result = []
    
    def backtrack(path: List[int], remaining: int, start: int) -> None:
        if remaining == 0:
            result.append(path[:])
            return
        
        for i in range(start, len(candidates)):
            candidate = candidates[i]
            
            # Pruning: if current candidate is too large, all subsequent will be too
            if candidate > remaining:
                break
            
            path.append(candidate)
            backtrack(path, remaining - candidate, i)  # i (not i+1) for reuse
            path.pop()
    
    backtrack([], target, 0)
    return result

# Palindrome Partitioning with memoization
class PalindromePartitioning:
    def __init__(self):
        self.palindrome_memo = {}
    
    def partition(self, s: str) -> List[List[str]]:
        """Partition string into palindromes"""
        result = []
        
        def is_palindrome(string: str) -> bool:
            if string in self.palindrome_memo:
                return self.palindrome_memo[string]
            
            result = string == string[::-1]
            self.palindrome_memo[string] = result
            return result
        
        def backtrack(path: List[str], start: int) -> None:
            if start == len(s):
                result.append(path[:])
                return
            
            for end in range(start + 1, len(s) + 1):
                substring = s[start:end]
                if is_palindrome(substring):
                    path.append(substring)
                    backtrack(path, end)
                    path.pop()
        
        backtrack([], 0)
        return result
                                
OCaml
(* N-Queens with optimized backtracking *)
module NQueensOptimized = struct
  type state = {
    cols: bool array;
    diag1: (int, bool) Hashtbl.t;
    diag2: (int, bool) Hashtbl.t;
  }
  
  let create_state n = {
    cols = Array.make n false;
    diag1 = Hashtbl.create n;
    diag2 = Hashtbl.create n;
  }
  
  let is_safe state row col =
    not state.cols.(col) &&
    not (Hashtbl.mem state.diag1 (row + col)) &&
    not (Hashtbl.mem state.diag2 (row - col))
  
  let place_queen state row col =
    state.cols.(col) <- true;
    Hashtbl.add state.diag1 (row + col) true;
    Hashtbl.add state.diag2 (row - col) true
  
  let remove_queen state row col =
    state.cols.(col) <- false;
    Hashtbl.remove state.diag1 (row + col);
    Hashtbl.remove state.diag2 (row - col)
  
  let solve_n_queens n =
    let solutions = ref [] in
    let board = Array.make n (-1) in
    let state = create_state n in
    
    let rec backtrack row =
      if row = n then
        solutions := (Array.copy board) :: !solutions
      else
        for col = 0 to n - 1 do
          if is_safe state row col then begin
            board.(row) <- col;
            place_queen state row col;
            backtrack (row + 1);
            remove_queen state row col;
            board.(row) <- -1
          end
        done
    in
    
    backtrack 0;
    !solutions
end

(* Word Break with memoization *)
module WordBreak = struct
  let word_break s word_dict =
    let memo = Hashtbl.create 100 in
    
    let rec can_break remaining =
      if Hashtbl.mem memo remaining then
        Hashtbl.find memo remaining
      else begin
        let result = 
          if String.length remaining = 0 then true
          else
            List.exists (fun word ->
              let word_len = String.length word in
              let remaining_len = String.length remaining in
              if word_len <= remaining_len &&
                 String.sub remaining 0 word_len = word then
                can_break (String.sub remaining word_len (remaining_len - word_len))
              else false
            ) word_dict
        in
        Hashtbl.add memo remaining result;
        result
      end
    in
    
    can_break s
  
  (* Word Break II - return all possible sentences *)
  let word_break_ii s word_dict =
    let memo_sentences = Hashtbl.create 100 in
    
    let rec backtrack remaining =
      if Hashtbl.mem memo_sentences remaining then
        Hashtbl.find memo_sentences remaining
      else begin
        let result = 
          if String.length remaining = 0 then [""]
          else
            List.fold_left (fun acc word ->
              let word_len = String.length word in
              let remaining_len = String.length remaining in
              if word_len <= remaining_len &&
                 String.sub remaining 0 word_len = word then begin
                let suffix = String.sub remaining word_len (remaining_len - word_len) in
                let suffix_sentences = backtrack suffix in
                List.fold_left (fun acc2 sentence ->
                  let new_sentence = 
                    if String.length sentence = 0 then word
                    else word ^ " " ^ sentence
                  in
                  new_sentence :: acc2
                ) acc suffix_sentences
              end else acc
            ) [] word_dict
        in
        Hashtbl.add memo_sentences remaining result;
        result
      end
    in
    
    backtrack s
end

(* Combination Sum with pruning *)
let combination_sum candidates target =
  let sorted_candidates = List.sort compare candidates in
  let result = ref [] in
  
  let rec backtrack path remaining start =
    if remaining = 0 then
      result := (List.rev path) :: !result
    else
      let rec try_candidates i candidates_rest =
        match candidates_rest with
        | [] -> ()
        | candidate :: rest ->
            if candidate > remaining then () (* Pruning *)
            else begin
              backtrack (candidate :: path) (remaining - candidate) i;
              try_candidates (i + 1) rest
            end
      in
      try_candidates start (List.drop sorted_candidates start)
  in
  
  backtrack [] target 0;
  !result

(* Sudoku Solver with constraint propagation *)
module SudokuSolver = struct
  type sudoku = int array array
  
  let is_valid board row col num =
    (* Check row *)
    let row_valid = not (Array.exists (fun x -> x = num) board.(row)) in
    
    (* Check column *)
    let col_valid = 
      let rec check r =
        if r >= 9 then true
        else if board.(r).(col) = num then false
        else check (r + 1)
      in
      check 0
    in
    
    (* Check 3x3 box *)
    let box_start_row = (row / 3) * 3 in
    let box_start_col = (col / 3) * 3 in
    let box_valid = 
      let rec check_box r c =
        if r >= box_start_row + 3 then true
        else if c >= box_start_col + 3 then check_box (r + 1) box_start_col
        else if board.(r).(c) = num then false
        else check_box r (c + 1)
      in
      check_box box_start_row box_start_col
    in
    
    row_valid && col_valid && box_valid
  
  let find_best_empty_cell board =
    let min