-
Notifications
You must be signed in to change notification settings - Fork 0
Backtracking
A comprehensive guide to backtracking algorithms with code implementations and problem-solving patterns.
- Basic Recursion Problems
- Subsequence Generation
- Permutation Problems
- Combination Problems
- Constraint Satisfaction Problems
- Sorting Algorithms
- Path Finding Problems
Problem: Print numbers from 1 to N using recursion Pattern: Simple recursion with base case
#include<iostream>
using namespace std;
void f(int n){
if(n==0) return;
f(n-1);
cout<<n<<" ";
}
int main(){
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)
Problem: Calculate nth Fibonacci number Pattern: Multiple recursive calls
#include<iostream>
using namespace std;
int fibonaki(int n){
if(n<=1){
return n;
}
return fibonaki(n-1)+fibonaki(n-2);
}
int main(){
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)
Problem: Calculate sum of numbers from 1 to N Pattern: Accumulator recursion
#include<iostream>
using namespace std;
int sum(int n){
if(n==0)
return 0;
return n+sum(n-1);
}
int main(){
int n;
cin>>n;
cout<<sum(n);
return 0;
}
Logic:
- Base case: sum(0) = 0
- Recurrence: sum(n) = n + sum(n-1)
- Time Complexity: O(n), Space Complexity: O(n)
Problem: Reverse an array using recursion Pattern: Two-pointer recursion
#include<iostream>
#include<vector>
void f(std::vector<int> &arr,int i,int j){
if(i>=j){
return;
}
std::swap(arr[i],arr[j]);
f(arr,i+1,j-1);
}
void reverse(std::vector<int> &arr){
int n=arr.size();
f(arr,0,n-1);
}
int main(){
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)
Problem: Check if string is palindrome using recursion Pattern: Two-pointer comparison
#include<iostream>
#include<string>
using namespace std;
bool isPalandrom(string s,int i,int j){
if(i>=j){
return true;
}
if(s[i]!=s[j]){
return false;
}
return isPalandrom(s,i+1,j-1);
}
int main(){
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)
Problem: Generate all possible subsequences of an array Pattern: Pick/Not Pick recursion
#include<iostream>
#include<vector>
std::vector<int> temp;
void f(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);
}
int main(){
std::vector<int> arr = {1,2,3};
f(arr, 0);
return 0;
}
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)
Problem: Find all subsequences with sum equal to K Pattern: Pick/Not Pick with constraint
#include<bits/stdc++.h>
using namespace std;
int k=4;
void f(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);
}
int main(){
vector<int> arr={1,2,3,4};
vector<int> dp;
f(0,arr,dp);
return 0;
}
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)
Problem: Count number of subsequences with sum K Pattern: Pick/Not Pick with counting
#include<bits/stdc++.h>
using namespace std;
int k=4;
int f(int i,vector<int> arr,vector<int> &res){
if(i==arr.size()){
int sum=accumulate(res.begin(),res.end(),0);
if(sum==k){
return 1;
}
return 0;
}
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;
}
int main(){
vector<int> arr={1,2,3,4};
vector<int> res;
cout<<f(0,arr,res);
return 0;
}
Logic:
- Return 1 if sum matches, 0 otherwise
- Add results from both recursive calls
- Time Complexity: O(2^n), Space Complexity: O(n)
Problem: Find first subsequence with sum K and stop Pattern: Pick/Not Pick with early termination
#include<bits/stdc++.h>
using namespace std;
int k=4;
bool f(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 true;
}
return false;
}
dp.push_back(arr[i]);
if(f(i+1,arr,dp)) return true;
dp.pop_back();
if(f(i+1,arr,dp)) return true;
return false;
}
int main(){
vector<int> arr={1,2,3,4};
vector<int> dp;
f(0,arr,dp);
return 0;
}
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)
Problem: Generate all permutations of a string Pattern: Fix one character, permute rest
#include<bits/stdc++.h>
void allcombination(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);
}
}
}
int main(){
std::string str = "abc";
std::unordered_set<int> s;
allcombination(str, "", s);
return 0;
}
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)
Problem: Generate all permutations of an array Pattern: Frequency array for tracking used elements
#include<bits/stdc++.h>
std::vector<std::vector<int>> ans;
void allArrayCombination(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;
}
}
}
int main(){
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;
}
return 0;
}
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)
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;
void findallcombination(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);
}
int main(){
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;
}
return 0;
}
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)
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;
void unique(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();
}
}
int main(){
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;
}
return 0;
}
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)
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);
void Dnquene(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;
}
}
}
int main(){
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;
}
return 0;
}
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)
Problem: Solve 9×9 Sudoku puzzle Pattern: Constraint satisfaction with validation
#include<bits/stdc++.h>
bool isValid(std::vector<std::vector<char>> &sudoko,int row,int col,char ch){
for(int i=0;i<9;i++){
if(sudoko[row][i]==ch) return false;
if(sudoko[i][col]==ch) return false;
if(sudoko[3*(row/3)+i/3][3*(col/3)+i%3]==ch) return false;
}
return true;
}
bool solve(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) return true;
else sudoko[i][j]='.';
}
}
return false;
}
}
}
return true;
}
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)
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;
bool isSafe(int node,std::unordered_map<int,std::unordered_set<int>> &graph,int col){
for(int i:graph[node]){
if(color[i]==col) return false;
}
return true;
}
bool solve(int node,std::unordered_map<int,std::unordered_set<int>> &graph,int M){
if(node==n) return true;
for(int i=0;i<M;i++){
if(isSafe(node,graph,i)){
color[node]=i;
if (solve(node+1,graph,M)==true) return true;
color[node]=-1;
}
}
return false;
}
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)
Problem: Sort array using divide and conquer Pattern: Divide, solve recursively, merge
#include<bits/stdc++.h>
void merge(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;
}
void mergesort(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)
Problem: Sort array using pivot partitioning Pattern: Partition around pivot, recursively sort
#include<bits/stdc++.h>
int partion(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;
}
void quicksort(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)
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;
void findlastCombination(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)
- Each recursive call represents a decision point
- Explore all possible decisions
- Backtrack when constraint violated or solution found
- Define state representation
- Define valid state transitions
- Implement constraint checking
- Pruning: Skip branches that can't lead to solution
- Early Termination: Stop when first solution found
- Constraint Propagation: Use constraints to reduce search space
Subset Generation Template:
void backtrack(int index, vector<int>& current) {
if (index == n) {
// Process current subset
return;
}
// Include current element
current.push_back(arr[index]);
backtrack(index + 1, current);
current.pop_back();
// Exclude current element
backtrack(index + 1, current);
}
Permutation Template:
void backtrack(vector<int>& current, vector<bool>& used) {
if (current.size() == n) {
// Process current permutation
return;
}
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:
bool backtrack(int position) {
if (position == target) {
return true; // Solution found
}
for (each possible value) {
if (isValid(position, value)) {
assign(position, value);
if (backtrack(position + 1)) {
return true;
}
unassign(position, value);
}
}
return false; // No solution found
}
This comprehensive guide covers all major backtracking patterns with working code examples and detailed explanations of the logic behind each approach.
- Twitter: @AlgoDocHub
- Facebook: AlgoDocHub
- Instagram: @AlgoDocHub
Contact Us: Have questions, suggestions, or feedback? Don't hesitate to reach out! You can contact the maintainers of AlgoDocHub by opening an issue or joining our Discord community.
Happy coding, and may your algorithms always run efficiently! *