Skip to content

Backtracking

codingdud edited this page Jul 16, 2025 · 1 revision

Backtracking Algorithms in C++

A comprehensive guide to backtracking algorithms with code implementations and problem-solving patterns.

Table of Contents

  1. Basic Recursion Problems
  2. Subsequence Generation
  3. Permutation Problems
  4. Combination Problems
  5. Constraint Satisfaction Problems
  6. Sorting Algorithms
  7. Path Finding Problems

Basic Recursion Problems

1. Print Numbers 1 to N

Problem: Print numbers from 1 to N using recursion Pattern: Simple recursion with base case

#include<iostream>usingnamespacestd;voidf(int n){
if(n==0) return;
f(n-1);
cout<<n<<"";
}
intmain(){
int n;
cin>>n;
f(n);
}

Logic:

  • Base case: n == 0
  • Recursive call first, then print (ensures ascending order)
  • Time Complexity: O(n), Space Complexity: O(n)

2. Fibonacci Series

Problem: Calculate nth Fibonacci number Pattern: Multiple recursive calls

#include<iostream>usingnamespacestd;intfibonaki(int n){
if(n<=1){
return n;
}
returnfibonaki(n-1)+fibonaki(n-2);
}
intmain(){
int n;
cin>>n;
cout<<fibonaki(n);
}

Logic:

  • Base cases: f(0)=0, f(1)=1
  • Recurrence: f(n) = f(n-1) + f(n-2)
  • Time Complexity: O(2^n), Space Complexity: O(n)

3. Sum of N Numbers

Problem: Calculate sum of numbers from 1 to N Pattern: Accumulator recursion

#include<iostream>usingnamespacestd;intsum(int n){
if(n==0)
return0;
return n+sum(n-1);
}
intmain(){
int n;
cin>>n;
cout<<sum(n);
return0;
}

Logic:

  • Base case: sum(0) = 0
  • Recurrence: sum(n) = n + sum(n-1)
  • Time Complexity: O(n), Space Complexity: O(n)

4. Array Reversal

Problem: Reverse an array using recursion Pattern: Two-pointer recursion

#include<iostream>
#include<vector>voidf(std::vector<int> &arr,int i,int j){
if(i>=j){
return;
}
std::swap(arr[i],arr[j]);
f(arr,i+1,j-1);
}
voidreverse(std::vector<int> &arr){
int n=arr.size();
f(arr,0,n-1);
}
intmain(){
std::vector<int> arr={1,2,3,4,5};
reverse(arr);
for(auto i:arr){
std::cout<<i<<"";
}
}

Logic:

  • Two pointers: start and end
  • Swap elements and move pointers inward
  • Base case: pointers meet or cross
  • Time Complexity: O(n), Space Complexity: O(n)

5. Palindrome Check

Problem: Check if string is palindrome using recursion Pattern: Two-pointer comparison

#include<iostream>
#include<string>usingnamespacestd;boolisPalandrom(string s,int i,int j){
if(i>=j){
returntrue;
}
if(s[i]!=s[j]){
returnfalse;
}
returnisPalandrom(s,i+1,j-1);
}
intmain(){
string s="abba";
cout<<isPalandrom(s,0,s.size()-1);
}

Logic:

  • Compare characters from both ends
  • If mismatch found, return false
  • If all characters match, return true
  • Time Complexity: O(n), Space Complexity: O(n)

Subsequence Generation

6. Generate All Subsequences

Problem: Generate all possible subsequences of an array Pattern: Pick/Not Pick recursion

#include<iostream>
#include<vector>
std::vector<int> temp;
voidf(std::vector<int> &arr,int i){
if(i==arr.size()){
for(int i:temp) std::cout<<i<<"";
std::cout<<"\n";
return;
}
temp.push_back(arr[i]);
f(arr,i+1);
temp.pop_back();
f(arr, i+1);
}
intmain(){
std::vector<int> arr = {1,2,3};
f(arr, 0);
return0;
}

Logic:

  • For each element: include it or exclude it
  • Two recursive calls: with and without current element
  • Time Complexity: O(2^n), Space Complexity: O(n)

7. Subsequences with Sum K

Problem: Find all subsequences with sum equal to K Pattern: Pick/Not Pick with constraint

#include<bits/stdc++.h>usingnamespacestd;int k=4;
voidf(int i,vector<int> arr,vector<int> &dp){
if(i==arr.size()){
int sum=accumulate(dp.begin(),dp.end(),0);
if(sum==k){
for(int i:dp){
cout<<i<<"";
}
cout<<endl;
}
return;
}
dp.push_back(arr[i]);
f(i+1,arr,dp);
dp.pop_back();
f(i+1,arr,dp);
}
intmain(){
vector<int> arr={1,2,3,4};
vector<int> dp;
f(0,arr,dp);
return0;
}

Logic:

  • Same pick/not pick pattern
  • Check sum at base case
  • Print only if sum equals target
  • Time Complexity: O(2^n), Space Complexity: O(n)

8. Count Subsequences with Sum K

Problem: Count number of subsequences with sum K Pattern: Pick/Not Pick with counting

#include<bits/stdc++.h>usingnamespacestd;int k=4;
intf(int i,vector<int> arr,vector<int> &res){
if(i==arr.size()){
int sum=accumulate(res.begin(),res.end(),0);
if(sum==k){
return1;
}
return0;
}
res.push_back(arr[i]);
int l=f(i+1,arr,res);
res.pop_back();
int r=f(i+1,arr,res);
return l+r;
}
intmain(){
vector<int> arr={1,2,3,4};
vector<int> res;
cout<<f(0,arr,res);
return0;
}

Logic:

  • Return 1 if sum matches, 0 otherwise
  • Add results from both recursive calls
  • Time Complexity: O(2^n), Space Complexity: O(n)

9. Find First Subsequence with Sum K

Problem: Find first subsequence with sum K and stop Pattern: Pick/Not Pick with early termination

#include<bits/stdc++.h>usingnamespacestd;int k=4;
boolf(int i,vector<int> arr,vector<int> &dp){
if(i==arr.size()){
int sum=accumulate(dp.begin(),dp.end(),0);
if(sum==k){
for(int i:dp){
cout<<i<<"";
}
cout<<endl;
returntrue;
}
returnfalse;
}
dp.push_back(arr[i]);
if(f(i+1,arr,dp)) returntrue;
dp.pop_back();
if(f(i+1,arr,dp)) returntrue;
returnfalse;
}
intmain(){
vector<int> arr={1,2,3,4};
vector<int> dp;
f(0,arr,dp);
return0;
}

Logic:

  • Return true when first valid subsequence found
  • Early termination prevents exploring other branches
  • Time Complexity: O(2^n) worst case, Space Complexity: O(n)

Permutation Problems

10. All String Permutations

Problem: Generate all permutations of a string Pattern: Fix one character, permute rest

#include<bits/stdc++.h>voidallcombination(std::string str,std::string dp,std::unordered_set<int> &s){
if(dp.size() == str.size()){
std::cout<<dp<<std::endl;
return;
}
for(int i=0;i<str.size();i++){
if(s.find(i)==s.end()){
s.insert(i);
allcombination(str, dp+str[i], s);
s.erase(i);
}
}
}
intmain(){
std::string str = "abc";
std::unordered_set<int> s;
allcombination(str, "", s);
return0;
}

Logic:

  • Use set to track used characters
  • Try each unused character at current position
  • Backtrack by removing from set
  • Time Complexity: O(n!), Space Complexity: O(n)

11. All Array Permutations

Problem: Generate all permutations of an array Pattern: Frequency array for tracking used elements

#include<bits/stdc++.h>
std::vector<std::vector<int>> ans;
voidallArrayCombination(std::vector<int> &arr,std::vector<int> &freq,std::vector<int> &dp){
if(arr.size()==dp.size()){
ans.push_back(dp);
return;
}
for(int i=0;i<arr.size();i++){
if(!freq[i]){
freq[i]=true;
dp.push_back(arr[i]);
allArrayCombination(arr,freq,dp);
dp.pop_back();
freq[i]=false;
}
}
}
intmain(){
std::vector<int> arr={1,2,3};
std::vector<int> freq(arr.size(), false);
std::vector<int> dp;
allArrayCombination(arr, freq, dp);
for(auto i:ans){
for(auto j:i){
std::cout<<j<<"";
}
std::cout<<std::endl;
}
return0;
}

Logic:

  • Frequency array tracks which elements are used
  • Try each unused element at current position
  • Backtrack by marking element as unused
  • Time Complexity: O(n!), Space Complexity: O(n)

Combination Problems

12. Combination Sum with Repetition

Problem: Find all combinations that sum to target (elements can be reused) Pattern: Include current element multiple times or skip

#include<bits/stdc++.h>
std::vector<std::vector<int>> result;
voidfindallcombination(std::vector<int> &arr,int i,std::vector<int> &dp,int k){
int sum=std::accumulate(dp.begin(),dp.end(),0);
if (i>=arr.size()||sum>=k){
if(sum==k) result.push_back(dp);
return;
}
dp.push_back(arr[i]);
findallcombination(arr,i,dp,k);
dp.pop_back();
findallcombination(arr,i+1,dp,k);
}
intmain(){
std::vector<int> arr={1,2,3,4,5};
int k=5;
std::vector<int> dp;
findallcombination(arr, 0, dp, k);
for(auto i:result){
for(auto j:i){
std::cout<<j<<"";
}
std::cout<<std::endl;
}
return0;
}

Logic:

  • Two choices: include current element again or move to next
  • Early termination when sum exceeds target
  • Time Complexity: Exponential, Space Complexity: O(target/min_element)

13. Unique Combinations

Problem: Generate all unique combinations from array with duplicates Pattern: Skip duplicates at same recursion level

#include<bits/stdc++.h>
std::vector<std::vector<int>> ans;
voidunique(int ind,std::vector<int> &arr,std ::vector<int> &dp){
if(ind==arr.size()){
ans.push_back(dp);
};
for(int i=ind;i<arr.size();i++){
if(i!=ind&&arr[i]==arr[i-1]) continue;
dp.push_back(arr[i]);
unique(i+1,arr,dp);
dp.pop_back();
}
}
intmain(){
std::vector<int> arr={1,1,1,2,3,3};
std::vector<int> dp;
unique(0, arr, dp);
for(auto i: ans){
for(auto j: i){
std::cout<<j<<"";
}
std::cout<<std::endl;
}
return0;
}

Logic:

  • Sort array first to group duplicates
  • Skip duplicates at same recursion level
  • Include each unique element once per level
  • Time Complexity: O(2^n), Space Complexity: O(n)

Constraint Satisfaction Problems

14. N-Queens Problem

Problem: Place N queens on N×N chessboard such that no two queens attack each other Pattern: Constraint satisfaction with backtracking

#include<bits/stdc++.h>int n=4;
std::vector<std::vector<std::vector<char>>> res;
std::vector<int> rowCheck(n,0),upperD(2*n-1,0),lowerD(2*n-1,0);
voidDnquene(int col,std::vector<std::vector<char>> &board){
if(col==board.size()){
res.push_back(board);
return;
}
for(int row=0;row<board.size();row++){
if(rowCheck[row]==0&&upperD[row+col]==0&&lowerD[board.size()-1+col-row]==0){
lowerD[board.size()-1+col-row]=1;
upperD[row+col]=1;
rowCheck[row]=1;
board[row][col]='Q';
Dnquene(col+1,board);
board[row][col]='.';
rowCheck[row]=0;
upperD[row+col]=0;
lowerD[board.size()-1+col-row]=0;
}
}
}
intmain(){
std::vector<std::vector<char>> board(n,std::vector<char>(n,'.'));
Dnquene(0,board);
for(auto i:res){
for(auto j:i){
for(auto k:j){
std::cout<<k<<"";
}
std::cout<<std::endl;
}
std::cout<<std::endl;
}
return0;
}

Logic:

  • Place queens column by column
  • Check row, upper diagonal, lower diagonal constraints
  • Use arrays to track attacked positions efficiently
  • Time Complexity: O(n!), Space Complexity: O(n)

15. Sudoku Solver

Problem: Solve 9×9 Sudoku puzzle Pattern: Constraint satisfaction with validation

#include<bits/stdc++.h>boolisValid(std::vector<std::vector<char>> &sudoko,int row,int col,char ch){
for(int i=0;i<9;i++){
if(sudoko[row][i]==ch) returnfalse;
if(sudoko[i][col]==ch) returnfalse;
if(sudoko[3*(row/3)+i/3][3*(col/3)+i%3]==ch) returnfalse;
}
returntrue;
}
boolsolve(std::vector<std::vector<char>> &sudoko){
for(int i=0;i<sudoko.size();i++){
for(int j=0;j<sudoko[0].size();j++){
if(sudoko[i][j]=='.'){
for(char ch='1';ch<='9';ch++){
if(isValid(sudoko,i,j,ch)){
sudoko[i][j]=ch;
if(solve(sudoko)==true) returntrue;
else sudoko[i][j]='.';
}
}
returnfalse;
}
}
}
returntrue;
}

Logic:

  • Find empty cell, try digits 1-9
  • Check row, column, and 3×3 box constraints
  • Backtrack if no valid digit found
  • Time Complexity: O(9^(nn)), Space Complexity: O(nn)

16. Graph Coloring

Problem: Color graph vertices with minimum colors such that no adjacent vertices have same color Pattern: Constraint satisfaction with adjacency check

#include<bits/stdc++.h>int n;
std::unordered_map<int,std::unordered_set<int>> graph;
std::vector<int> color;
boolisSafe(int node,std::unordered_map<int,std::unordered_set<int>> &graph,int col){
for(int i:graph[node]){
if(color[i]==col) returnfalse;
}
returntrue;
}
boolsolve(int node,std::unordered_map<int,std::unordered_set<int>> &graph,int M){
if(node==n) returntrue;
for(int i=0;i<M;i++){
if(isSafe(node,graph,i)){
color[node]=i;
if (solve(node+1,graph,M)==true) returntrue;
color[node]=-1;
}
}
returnfalse;
}

Logic:

  • Try each color for current vertex
  • Check if color conflicts with adjacent vertices
  • Backtrack if no valid color found
  • Time Complexity: O(M^n), Space Complexity: O(n)

Sorting Algorithms

17. Merge Sort

Problem: Sort array using divide and conquer Pattern: Divide, solve recursively, merge

#include<bits/stdc++.h>voidmerge(std::vector<int> &arr,int i,int m,int j){
std::vector<int> temp;
int l=i,r=m+1;
while(l<=m&&r<=j){
if(arr[l]<arr[r]){
temp.push_back(arr[l++]);
}else{
temp.push_back(arr[r++]);
}
}
while(l<=m) temp.push_back(arr[l++]);
while(r<=j) temp.push_back(arr[r++]);
l=0;
for(int k=i;k<=j;k++){
arr[k]=temp[l++];
}
return;
}
voidmergesort(std::vector<int> &arr,int i,int j){
if(i>=j) return;
int m=i+(j-i)/2;
mergesort(arr,i,m);
mergesort(arr,m+1,j);
merge(arr,i,m,j);
}

Logic:

  • Divide array into two halves
  • Recursively sort both halves
  • Merge sorted halves
  • Time Complexity: O(n log n), Space Complexity: O(n)

18. Quick Sort

Problem: Sort array using pivot partitioning Pattern: Partition around pivot, recursively sort

#include<bits/stdc++.h>intpartion(std::vector<int> &arr,int i,int j){
int l=i;
i+=1;
while(i<j){
while(arr[i]<arr[l]) i++;
while(arr[j]>arr[l]) j--;
if(i<j){
std::swap(arr[i],arr[j]);
i++;
j--;
}
}
std::swap(arr[j],arr[l]);
return j;
}
voidquicksort(std::vector<int> &arr,int low,int high){
if(low<high){
int piv=partion(arr,low,high);
quicksort(arr,low,piv-1);
quicksort(arr,piv+1,high);
}
}

Logic:

  • Choose pivot, partition array around it
  • Recursively sort left and right subarrays
  • In-place sorting algorithm
  • Time Complexity: O(n log n) average, O(n²) worst, Space Complexity: O(log n)

Path Finding Problems

19. Rat in Maze

Problem: Find all paths from top-left to bottom-right in maze Pattern: 4-directional DFS with backtracking

#include<bits/stdc++.h>
std::vector<std::vector<int>> maze;
std::vector<std::vector<std::vector<int>>> ans;
int n,m;
int count=0;
voidfindlastCombination(std::vector<std::vector<int>> &maze,int i,int j){
if(i==n-1&&j==m-1){
count++;
ans.push_back(maze);
return;
}
if((i+1)<n&&maze[i+1][j]==0){
maze[i+1][j]=8;
findlastCombination(maze,i+1,j);
maze[i+1][j]=0;
}
if((j-1)>=0&&maze[i][j-1]==0){
maze[i][j-1]=8;
findlastCombination(maze,i,j-1);
maze[i][j-1]=0;
}
if((j+1)<m&&maze[i][j+1]==0){
maze[i][j+1]=8;
findlastCombination(maze,i,j+1);
maze[i][j+1]=0;
}
if((i-1)>=0&&maze[i-1][j]==0){
maze[i-1][j]=8;
findlastCombination(maze,i-1,j);
maze[i-1][j]=0;
}
}

Logic:

  • Explore all 4 directions (up, down, left, right)
  • Mark visited cells, backtrack after exploring
  • Count all possible paths to destination
  • Time Complexity: O(4^(mn)), Space Complexity: O(mn)

Key Backtracking Patterns Summary

1. Decision Tree Pattern

  • Each recursive call represents a decision point
  • Explore all possible decisions
  • Backtrack when constraint violated or solution found

2. State Space Search

  • Define state representation
  • Define valid state transitions
  • Implement constraint checking

3. Optimization Techniques

  • Pruning: Skip branches that can't lead to solution
  • Early Termination: Stop when first solution found
  • Constraint Propagation: Use constraints to reduce search space

4. Common Templates

Subset Generation Template:

voidbacktrack(int index, vector<int>& current) {
if (index == n) {
// Process current subsetreturn;
}
// Include current element
current.push_back(arr[index]);
backtrack(index + 1, current);
current.pop_back();
// Exclude current elementbacktrack(index + 1, current);
}

Permutation Template:

voidbacktrack(vector<int>& current, vector<bool>& used) {
if (current.size() == n) {
// Process current permutationreturn;
}
for (int i = 0; i < n; i++) {
if (!used[i]) {
used[i] = true;
current.push_back(arr[i]);
backtrack(current, used);
current.pop_back();
used[i] = false;
}
}
}

Constraint Satisfaction Template:

boolbacktrack(int position) {
if (position == target) {
returntrue; // Solution found
}
for (each possible value) {
if (isValid(position, value)) {
assign(position, value);
if (backtrack(position + 1)) {
returntrue;
}
unassign(position, value);
}
}
returnfalse; // No solution found
}

This comprehensive guide covers all major backtracking patterns with working code examples and detailed explanations of the logic behind each approach.

Clone this wiki locally