Complete Search

Complete search (also known as brute force) is a problem-solving technique that examines all possible solutions to find the correct answer. While not always the most efficient, it guarantees finding the optimal solution when implemented correctly.

When to Use Complete Search

Complete search is ideal when:

Good Candidates for Complete Search:

  • Small input size (typically n ≤ 20-25)
  • No obvious pattern or mathematical formula
  • All possible solutions must be examined
  • Time limit allows for exponential algorithms

Generating All Subsets

A subset is a collection of elements from a set. For a set of n elements, there are 2^n possible subsets:

Recursive Approach


void generateSubsets(vector<int>& nums, vector<int>& current, int index) {
    // Base case: we've considered all elements
    if (index == nums.size()) {
        // Print or process the current subset
        for (int x : current) {
            cout << x << " ";
        }
        cout << endl;
        return;
    }
    
    // Choice 1: Don't include nums[index]
    generateSubsets(nums, current, index + 1);
    
    // Choice 2: Include nums[index]
    current.push_back(nums[index]);
    generateSubsets(nums, current, index + 1);
    current.pop_back(); // Backtrack
}

// Usage
vector<int> nums = {1, 2, 3};
vector<int> current;
generateSubsets(nums, current, 0);
                    

Bit Manipulation Approach


void generateSubsetsBit(vector<int>& nums) {
    int n = nums.size();
    
    // Iterate through all possible bitmasks
    for (int mask = 0; mask < (1 << n); mask++) {
        vector<int> subset;
        
        for (int i = 0; i < n; i++) {
            // Check if i-th bit is set
            if (mask & (1 << i)) {
                subset.push_back(nums[i]);
            }
        }
        
        // Process subset
        for (int x : subset) {
            cout << x << " ";
        }
        cout << endl;
    }
}
                    

Generating All Permutations

A permutation is an arrangement of elements. For n distinct elements, there are n! permutations:


void generatePermutations(vector<int>& nums, vector<bool>& used, vector<int>& current) {
    if (current.size() == nums.size()) {
        // We have a complete permutation
        for (int x : current) {
            cout << x << " ";
        }
        cout << endl;
        return;
    }
    
    for (int i = 0; i < nums.size(); i++) {
        if (!used[i]) {
            // Choose nums[i]
            used[i] = true;
            current.push_back(nums[i]);
            
            // Recurse
            generatePermutations(nums, used, current);
            
            // Backtrack
            current.pop_back();
            used[i] = false;
        }
    }
}

// Using STL next_permutation (easier approach)
void generatePermutationsSTL(vector<int> nums) {
    sort(nums.begin(), nums.end());
    do {
        for (int x : nums) {
            cout << x << " ";
        }
        cout << endl;
    } while (next_permutation(nums.begin(), nums.end()));
}
                    

Backtracking Template

Backtracking is a systematic way to explore all possibilities:


void backtrack(State& current, vector<Solution>& solutions) {
    if (isComplete(current)) {
        solutions.push_back(current);
        return;
    }
    
    for (Choice choice : getPossibleChoices(current)) {
        if (isValid(current, choice)) {
            // Make the choice
            makeChoice(current, choice);
            
            // Recurse
            backtrack(current, solutions);
            
            // Undo the choice (backtrack)
            undoChoice(current, choice);
        }
    }
}
                    

Example: N-Queens Problem

Place N queens on an N×N chessboard so that no two queens attack each other:


class NQueens {
    vector<vector<string>> solutions;
    vector<string> board;
    
public:
    vector<vector<string>> solveNQueens(int n) {
        board = vector<string>(n, string(n, '.'));
        backtrack(0, n);
        return solutions;
    }
    
private:
    void backtrack(int row, int n) {
        if (row == n) {
            solutions.push_back(board);
            return;
        }
        
        for (int col = 0; col < n; col++) {
            if (isValid(row, col, n)) {
                board[row][col] = 'Q';
                backtrack(row + 1, n);
                board[row][col] = '.';
            }
        }
    }
    
    bool isValid(int row, int col, int n) {
        // Check column
        for (int i = 0; i < row; i++) {
            if (board[i][col] == 'Q') return false;
        }
        
        // Check diagonal
        for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
            if (board[i][j] == 'Q') return false;
        }
        
        // Check anti-diagonal
        for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
            if (board[i][j] == 'Q') return false;
        }
        
        return true;
    }
};
                    

Optimization Techniques

Make complete search faster with these techniques:

Pruning Strategies

  • Early termination: Stop exploring if current path can't lead to solution
  • Constraint checking: Validate constraints as early as possible
  • Ordering: Try most promising choices first
  • Memoization: Cache results of subproblems
  • Symmetry breaking: Avoid exploring symmetric states

Time Complexity Analysis

Complexity Warning

  • Subsets: O(2^n) - doubles with each new element
  • Permutations: O(n!) - grows extremely fast
  • General backtracking: Often exponential
  • Rule of thumb: Complete search is usually feasible only for n ≤ 20-25

CodeForces Challenge Problems

Practice complete search with these problems:

Medium

Generating Subsets

Practice generating all possible subsets of a given set.

Backtracking Recursion
Solve Problem