0% found this document useful (0 votes)
2 views12 pages

Chapter 10 Backtracking in Java

Chapter 10 discusses backtracking in Java, describing it as a recursive method that explores all choices while allowing for undoing bad decisions. It presents various patterns for solving problems using backtracking, such as generating subsets, combination sums, permutations, and solving puzzles like N-Queens and Sudoku. The chapter provides code examples and outlines the time and space complexities for each pattern.

Uploaded by

quantalgo.labs
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views12 pages

Chapter 10 Backtracking in Java

Chapter 10 discusses backtracking in Java, describing it as a recursive method that explores all choices while allowing for undoing bad decisions. It presents various patterns for solving problems using backtracking, such as generating subsets, combination sums, permutations, and solving puzzles like N-Queens and Sudoku. The chapter provides code examples and outlines the time and space complexities for each pattern.

Uploaded by

quantalgo.labs
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

Chapter 10

Backtracking in Java
Java Data Structures & Algorithms Series

Backtracking is recursion with an undo step — you explore a choice, recurse deeper, then undo that
choice and try the next option. It systematically explores all possibilities while pruning invalid
paths early.

1 Backtracking vs Recursion vs DP

Recursion: Solve by breaking into subproblems (no undo needed)


Backtracking: Explore ALL paths, UNDO bad choices ← this chapter
DP: Overlapping subproblems, store results (no undo)

Backtracking = Recursion + Pruning + Undo

The Universal Backtracking Template

void backtrack(state, choices) {


if (goalReached(state)) {
[Link](copy of state); // ← snapshot, NOT reference!
return;
}

for (choice : availableChoices) {


if (isValid(choice)) {
makeChoice(choice); // 1. Choose
backtrack(state, next); // 2. Explore
undoChoice(choice); // 3. Un-choose ← KEY STEP
}
}
}

⭐ The Three-Step Loop:


1. makeChoice() — add to current state
2. backtrack() — go deeper
3. undoChoice() — restore state exactly as it was before step 1

If step 3 is missing or wrong, you get incorrect or duplicate results.

2 Pattern 1 — Subsets (No Duplicates)

Problem: Return all possible subsets of a set with unique integers.


nums = [1,2,3]
Answer: [ [], [1], [2], [3], [1,2], [1,3], [2,3], [1,2,3] ]

public static List<List<Integer>> subsets(int[] nums) {


List<List<Integer>> result = new ArrayList<>();
backtrackSubsets(nums, 0, new ArrayList<>(), result);
return result;
}

private static void backtrackSubsets(int[] nums, int start,


List<Integer> current,
List<List<Integer>> result) {
[Link](new ArrayList<>(current)); // snapshot at EVERY node = valid
subset

for (int i = start; i < [Link]; i++) {


[Link](nums[i]); // choose
backtrackSubsets(nums, i + 1, current, result); // explore
[Link]([Link]() - 1); // un-choose
}
}

// Time: O(2^n × n) | Space: O(n)

3 Pattern 2 — Subsets II (With Duplicates)

Problem: Array may contain duplicates. Return only unique subsets.

nums = [1,2,2]
Answer: [ [], [1], [2], [1,2], [2,2], [1,2,2] ] ← no duplicate [2]

public static List<List<Integer>> subsetsWithDup(int[] nums) {


[Link](nums); // sort first to group duplicates adjacent
List<List<Integer>> result = new ArrayList<>();
backtrackSubsDup(nums, 0, new ArrayList<>(), result);
return result;
}

private static void backtrackSubsDup(int[] nums, int start,


List<Integer> current,
List<List<Integer>> result) {
[Link](new ArrayList<>(current));

for (int i = start; i < [Link]; i++) {


if (i > start && nums[i] == nums[i-1]) continue; // skip duplicate branch

[Link](nums[i]);
backtrackSubsDup(nums, i + 1, current, result);
[Link]([Link]() - 1);
}
}

// Time: O(2^n × n) | Space: O(n)


⭐ Duplicate-Skip Rule: if (i > start && nums[i] == nums[i-1]) continue
This skips making the same choice again at the SAME recursion level.
It does NOT skip the element across different levels — that is intentional.

4 Pattern 3 — Combination Sum I (Reuse Allowed)

Problem: Find all combinations summing to target. Each number can be used unlimited times.

candidates=[2,3,6,7], target=7
Answer: [ [2,2,3], [7] ]

public static List<List<Integer>> combinationSum(int[] candidates, int target) {


[Link](candidates);
List<List<Integer>> result = new ArrayList<>();
backtrackComb(candidates, 0, target, new ArrayList<>(), result);
return result;
}

private static void backtrackComb(int[] candidates, int start, int remaining,


List<Integer> current,
List<List<Integer>> result) {
if (remaining == 0) {
[Link](new ArrayList<>(current));
return;
}

for (int i = start; i < [Link]; i++) {


if (candidates[i] > remaining) break; // sorted → prune rest

[Link](candidates[i]);
backtrackComb(candidates, i, remaining - candidates[i], current, result);
// ↑ pass i (not i+1) to allow reusing the same element
[Link]([Link]() - 1);
}
}

// Time: O(2^target) | Space: O(target / min_candidate)

5 Pattern 4 — Combination Sum II (No Reuse, With Duplicates)

Problem: Each number used at most once. Array may have duplicates. Return unique combinations.

candidates=[10,1,2,7,6,1,5], target=8
Answer: [ [1,1,6], [1,2,5], [1,7], [2,6] ]

public static List<List<Integer>> combinationSum2(int[] candidates, int target) {


[Link](candidates);
List<List<Integer>> result = new ArrayList<>();
backtrackComb2(candidates, 0, target, new ArrayList<>(), result);
return result;
}

private static void backtrackComb2(int[] candidates, int start, int remaining,


List<Integer> current,
List<List<Integer>> result) {
if (remaining == 0) {
[Link](new ArrayList<>(current));
return;
}

for (int i = start; i < [Link]; i++) {


if (candidates[i] > remaining) break; // pruning
if (i > start && candidates[i] == candidates[i-1]) continue; // skip dup

[Link](candidates[i]);
backtrackComb2(candidates, i + 1, remaining - candidates[i], current,
result);
// ↑ pass i+1 (not i) because each element used at most once
[Link]([Link]() - 1);
}
}

// Time: O(2^n × n) | Space: O(n)

6 Pattern 5 — Permutations I (No Duplicates)

public static List<List<Integer>> permute(int[] nums) {


List<List<Integer>> result = new ArrayList<>();
backtrackPerm(nums, new ArrayList<>(), new boolean[[Link]], result);
return result;
}

private static void backtrackPerm(int[] nums, List<Integer> current,


boolean[] used, List<List<Integer>> result) {
if ([Link]() == [Link]) {
[Link](new ArrayList<>(current));
return;
}

for (int i = 0; i < [Link]; i++) {


if (used[i]) continue;

used[i] = true;
[Link](nums[i]);
backtrackPerm(nums, current, used, result);
[Link]([Link]() - 1); // un-choose
used[i] = false; // un-mark
}
}

// Time: O(n! × n) | Space: O(n)


7 Pattern 6 — Permutations II (With Duplicates)

Problem: Array may have duplicates. Return only unique permutations.

nums = [1,1,2]
Answer: [ [1,1,2], [1,2,1], [2,1,1] ]

public static List<List<Integer>> permuteUnique(int[] nums) {


[Link](nums); // sort to group duplicates
List<List<Integer>> result = new ArrayList<>();
backtrackPermUniq(nums, new ArrayList<>(), new boolean[[Link]], result);
return result;
}

private static void backtrackPermUniq(int[] nums, List<Integer> current,


boolean[] used, List<List<Integer>> result) {
if ([Link]() == [Link]) {
[Link](new ArrayList<>(current));
return;
}

for (int i = 0; i < [Link]; i++) {


if (used[i]) continue;
// Allow nums[i] only if nums[i-1] (same value) was used in this branch
if (i > 0 && nums[i] == nums[i-1] && !used[i-1]) continue;

used[i] = true;
[Link](nums[i]);
backtrackPermUniq(nums, current, used, result);
[Link]([Link]() - 1);
used[i] = false;
}
}

// Time: O(n! × n) | Space: O(n)

8 Pattern 7 — N-Queens

Problem: Place N queens on an N×N board so no two queens share a row, column, or diagonal.

N=4 has 2 solutions:


. Q . . . . Q .
. . . Q or Q . . .
Q . . . . . . Q
. . Q . . Q . .

public static List<List<String>> solveNQueens(int n) {


List<List<String>> result = new ArrayList<>();
char[][] board = new char[n][n];
for (char[] row : board) [Link](row, '.');
backtrackQueens(board, 0, result);
return result;
}
private static void backtrackQueens(char[][] board, int row,
List<List<String>> result) {
if (row == [Link]) {
List<String> sol = new ArrayList<>();
for (char[] r : board) [Link](new String(r));
[Link](sol);
return;
}

for (int col = 0; col < [Link]; col++) {


if (isSafe(board, row, col)) {
board[row][col] = 'Q'; // place
backtrackQueens(board, row + 1, result);
board[row][col] = '.'; // remove (backtrack)
}
}
}

private static boolean isSafe(char[][] board, int row, int col) {


int n = [Link];
for (int i = 0; i < row; i++)
if (board[i][col] == 'Q') return false; // column check
for (int i=row-1, j=col-1; i>=0 && j>=0; i--, j--)
if (board[i][j] == 'Q') return false; // upper-left diagonal
for (int i=row-1, j=col+1; i>=0 && j<n; i--, j++)
if (board[i][j] == 'Q') return false; // upper-right diagonal
return true;
}

// Time: O(n!) | Space: O(n²)

9 Pattern 8 — Sudoku Solver

Problem: Fill a 9×9 Sudoku grid so every row, column, and 3×3 box contains digits 1–9 exactly once.

public static void solveSudoku(char[][] board) {


solve(board);
}

private static boolean solve(char[][] board) {


for (int row = 0; row < 9; row++) {
for (int col = 0; col < 9; col++) {
if (board[row][col] != '.') continue; // cell already filled

for (char num = '1'; num <= '9'; num++) {


if (isValid(board, row, col, num)) {
board[row][col] = num; // place digit
if (solve(board)) return true; // recurse — found solution
board[row][col] = '.'; // backtrack
}
}
return false; // no digit works → signal caller to backtrack
}
}
return true; // all cells filled → solution found
}
private static boolean isValid(char[][] board, int row, int col, char num) {
for (int i = 0; i < 9; i++) {
if (board[row][i] == num) return false; // row conflict
if (board[i][col] == num) return false; // column conflict
int boxRow = 3 * (row / 3) + i / 3;
int boxCol = 3 * (col / 3) + i % 3;
if (board[boxRow][boxCol] == num) return false; // 3×3 box conflict
}
return true;
}

// Time: O(9^empty_cells) | Space: O(81) = O(1)

10 Pattern 9 — Word Search

Problem: Find if a word exists in a 2D board using adjacent (up/down/left/right) cells. Each cell
used once.

board=[["A","B","C","E"],
["S","F","C","S"],
["A","D","E","E"]]
word="ABCCED" → true

public static boolean exist(char[][] board, String word) {


int m = [Link], n = board[0].length;
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
if (dfsWord(board, word, i, j, 0)) return true;
return false;
}

private static boolean dfsWord(char[][] board, String word,


int r, int c, int idx) {
if (idx == [Link]()) return true; // all chars matched

if (r < 0 || r >= [Link] ||


c < 0 || c >= board[0].length) return false; // out of bounds
if (board[r][c] != [Link](idx)) return false; // char mismatch

char temp = board[r][c];


board[r][c] = '#'; // mark visited — prevent reuse in same path

boolean found = dfsWord(board, word, r+1, c, idx+1) ||


dfsWord(board, word, r-1, c, idx+1) ||
dfsWord(board, word, r, c+1, idx+1) ||
dfsWord(board, word, r, c-1, idx+1);

board[r][c] = temp; // restore cell (backtrack)


return found;
}

// Time: O(m × n × 4^L) L = word length | Space: O(L)


11 Pattern 10 — Palindrome Partitioning

Problem: Partition a string so every substring in the partition is a palindrome.

s = "aab"
Answer: [ ["a","a","b"], ["aa","b"] ]

public static List<List<String>> partition(String s) {


List<List<String>> result = new ArrayList<>();
backtrackPalin(s, 0, new ArrayList<>(), result);
return result;
}

private static void backtrackPalin(String s, int start,


List<String> current,
List<List<String>> result) {
if (start == [Link]()) {
[Link](new ArrayList<>(current));
return;
}

for (int end = start + 1; end <= [Link](); end++) {


String sub = [Link](start, end);
if (isPalin(sub)) {
[Link](sub); // choose
backtrackPalin(s, end, current, result); // explore
[Link]([Link]() - 1); // un-choose
}
}
}

private static boolean isPalin(String s) {


int l = 0, r = [Link]() - 1;
while (l < r) if ([Link](l++) != [Link](r--)) return false;
return true;
}

// Time: O(n × 2^n) | Space: O(n)

12 Pattern 11 — Letter Combinations of a Phone Number

digits = "23"
Answer: ["ad","ae","af","bd","be","bf","cd","ce","cf"]

public static List<String> letterCombinations(String digits) {


if ([Link]()) return new ArrayList<>();

String[] phone = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};


List<String> result = new ArrayList<>();
backtrackPhone(digits, 0, new StringBuilder(), phone, result);
return result;
}

private static void backtrackPhone(String digits, int idx,


StringBuilder current,
String[] phone, List<String> result) {
if (idx == [Link]()) {
[Link]([Link]());
return;
}

String letters = phone[[Link](idx) - '0'];


for (char c : [Link]()) {
[Link](c); // choose
backtrackPhone(digits, idx + 1, current, phone, result); // explore
[Link]([Link]() - 1); // un-choose
}
}

// Time: O(4^n × n) | Space: O(n)

13 Backtracking Pruning Strategies

The key to efficient backtracking is aggressive pruning — cutting branches early rather than
exploring them to the end.

Strategy How to Apply Example


Sort first [Link]() before backtracking Break when candidates[i] >
remaining
Skip duplicates if (i > start && nums[i] == nums[i- Subsets II, Combination Sum II
1])
Early termination if (candidates[i] > remaining) Combination Sum I / II
break
Visited array boolean[] used or mark cell with Permutations, Word Search
#
Bounds check if (r < 0 || c < 0 || ...) return Flood Fill, Word Search, N-
Queens

14 Full Runnable Java Program

import [Link].*;

public class Chapter10Backtracking {

public static void main(String[] args) {


[Link]("Subsets [1,2,3]: " + subsets(new int[]
{1,2,3}));
[Link]("Subsets w/dup [1,2,2]: " + subsetsWithDup(new int[]
{1,2,2}));
[Link]("CombSum [2,3,6,7] t=7: " + combinationSum(new int[]
{2,3,6,7}, 7));
[Link]("CombSum2 [10,1,2,7,6,1,5] t=8: " + combinationSum2(new
int[]{10,1,2,7,6,1,5}, 8));
[Link]("Permutations [1,2,3]: " + permute(new int[]
{1,2,3}));
[Link]("PermuteUnique [1,1,2]: " + permuteUnique(new int[]
{1,1,2}));
[Link]("N-Queens N=4: " + solveNQueens(4));

char[][] board = {{'A','B','C','E'},{'S','F','C','S'},{'A','D','E','E'}};


[Link]("Word ABCCED: " + exist(board, "ABCCED"));
// true

[Link]("Palindrome partition 'aab': " + partition("aab"));


[Link]("Phone '23': " +
letterCombinations("23"));
}

static List<List<Integer>> subsets(int[] n){List<List<Integer>> r=new


ArrayList<>();btS(n,0,new ArrayList<>(),r);return r;}
static void btS(int[] n,int s,List<Integer> c,List<List<Integer>> r){[Link](new
ArrayList<>(c));for(int i=s;i<[Link];i++)
{[Link](n[i]);btS(n,i+1,c,r);[Link]([Link]()-1);}}

static List<List<Integer>> subsetsWithDup(int[] n)


{[Link](n);List<List<Integer>> r=new ArrayList<>();btSD(n,0,new
ArrayList<>(),r);return r;}
static void btSD(int[] n,int s,List<Integer> c,List<List<Integer>> r){[Link](new
ArrayList<>(c));for(int i=s;i<[Link];i++){if(i>s&&n[i]==n[i-
1])continue;[Link](n[i]);btSD(n,i+1,c,r);[Link]([Link]()-1);}}

static List<List<Integer>> combinationSum(int[] c,int t)


{[Link](c);List<List<Integer>> r=new ArrayList<>();btC(c,0,t,new
ArrayList<>(),r);return r;}
static void btC(int[] c,int s,int rem,List<Integer> cur,List<List<Integer>> r)
{if(rem==0){[Link](new ArrayList<>(cur));return;}for(int i=s;i<[Link];i++)
{if(c[i]>rem)break;[Link](c[i]);btC(c,i,rem-c[i],cur,r);[Link]([Link]()-1);}}

static List<List<Integer>> combinationSum2(int[] c,int t)


{[Link](c);List<List<Integer>> r=new ArrayList<>();btC2(c,0,t,new
ArrayList<>(),r);return r;}
static void btC2(int[] c,int s,int rem,List<Integer> cur,List<List<Integer>> r)
{if(rem==0){[Link](new ArrayList<>(cur));return;}for(int i=s;i<[Link];i++)
{if(c[i]>rem)break;if(i>s&&c[i]==c[i-1])continue;[Link](c[i]);btC2(c,i+1,rem-
c[i],cur,r);[Link]([Link]()-1);}}

static List<List<Integer>> permute(int[] nums){List<List<Integer>> r=new


ArrayList<>();btP(nums,new ArrayList<>(),new boolean[[Link]],r);return r;}
static void btP(int[] n,List<Integer> c,boolean[] u,List<List<Integer>> r)
{if([Link]()==[Link]){[Link](new ArrayList<>(c));return;}for(int i=0;i<[Link];i+
+){if(u[i])continue;u[i]=true;[Link](n[i]);btP(n,c,u,r);[Link]([Link]()-
1);u[i]=false;}}

static List<List<Integer>> permuteUnique(int[] nums)


{[Link](nums);List<List<Integer>> r=new ArrayList<>();btPU(nums,new
ArrayList<>(),new boolean[[Link]],r);return r;}
static void btPU(int[] n,List<Integer> c,boolean[] u,List<List<Integer>> r)
{if([Link]()==[Link]){[Link](new ArrayList<>(c));return;}for(int i=0;i<[Link];i+
+){if(u[i])continue;if(i>0&&n[i]==n[i-1]&&!u[i-
1])continue;u[i]=true;[Link](n[i]);btPU(n,c,u,r);[Link]([Link]()-1);u[i]=false;}}

static List<List<String>> solveNQueens(int n){List<List<String>> r=new


ArrayList<>();char[][] b=new char[n][n];for(char[]
row:b)[Link](row,'.');btQ(b,0,r);return r;}
static void btQ(char[][] b,int row,List<List<String>> r){if(row==[Link])
{List<String> s=new ArrayList<>();for(char[] c:b)[Link](new
String(c));[Link](s);return;}for(int c=0;c<[Link];c++){if(safe(b,row,c)){b[row]
[c]='Q';btQ(b,row+1,r);b[row][c]='.';}}}
static boolean safe(char[][] b,int r,int c){int n=[Link];for(int i=0;i<r;i+
+)if(b[i][c]=='Q')return false;for(int i=r-1,j=c-1;i>=0&&j>=0;i--,j--)if(b[i]
[j]=='Q')return false;for(int i=r-1,j=c+1;i>=0&&j<n;i--,j++)if(b[i][j]=='Q')return
false;return true;}

static boolean exist(char[][] b,String w){for(int i=0;i<[Link];i++)for(int


j=0;j<b[0].length;j++)if(dfs(b,w,i,j,0))return true;return false;}
static boolean dfs(char[][] b,String w,int r,int c,int idx)
{if(idx==[Link]())return true;if(r<0||r>=[Link]||c<0||c>=b[0].length||b[r][c]!
=[Link](idx))return false;char t=b[r][c];b[r][c]='#';boolean
f=dfs(b,w,r+1,c,idx+1)||dfs(b,w,r-1,c,idx+1)||dfs(b,w,r,c+1,idx+1)||dfs(b,w,r,c-
1,idx+1);b[r][c]=t;return f;}

static List<List<String>> partition(String s){List<List<String>> r=new


ArrayList<>();btPalin(s,0,new ArrayList<>(),r);return r;}
static void btPalin(String s,int st,List<String> c,List<List<String>> r)
{if(st==[Link]()){[Link](new ArrayList<>(c));return;}for(int
e=st+1;e<=[Link]();e++){String sub=[Link](st,e);if(isPalin(sub))
{[Link](sub);btPalin(s,e,c,r);[Link]([Link]()-1);}}}
static boolean isPalin(String s){int l=0,r=[Link]()-1;while(l<r)if([Link](l+
+)!=[Link](r--))return false;return true;}

static List<String> letterCombinations(String d){if([Link]())return new


ArrayList<>();String[]
ph={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};List<String> r=new
ArrayList<>();btPh(d,0,new StringBuilder(),ph,r);return r;}
static void btPh(String d,int idx,StringBuilder c,String[] ph,List<String> r)
{if(idx==[Link]()){[Link]([Link]());return;}for(char
ch:ph[[Link](idx)-'0'].toCharArray())
{[Link](ch);btPh(d,idx+1,c,ph,r);[Link]([Link]()-1);}}
}

15 Practice Problems for Chapter 10

Solve in this order:

Difficulty Problem
Medium Subsets (LeetCode #78)
Medium Subsets II (LeetCode #90)
Medium Combination Sum I (LeetCode #39)
Medium Combination Sum II (LeetCode #40)
Medium Permutations (LeetCode #46)
Medium Permutations II (LeetCode #47)
Medium Letter Combinations of Phone Number (LeetCode
#17)
Medium Palindrome Partitioning (LeetCode #131)
Medium Word Search (LeetCode #79)
Hard N-Queens (LeetCode #51)
Hard Sudoku Solver (LeetCode #37)

16 Backtracking Problem Classification

Problem Type Trigger Keywords Key Template Point


Subsets All subsets, power set Add snapshot at EVERY node
Combinations Choose K from N, sum to target Add snapshot only at leaves
Permutations All arrangements, all orders Use boolean used[] array
Grid problems 2D board, path, connected region Mark visited (#), restore after
Constraint satisfaction N-Queens, Sudoku, valid Check validity BEFORE placing
placement
String partition Split into valid Try every split point
palindromes/words end=start+1..n

💡 Key Insight: Two bugs account for 90% of wrong backtracking answers:

Bug 1 — Wrong snapshot: [Link](current) instead of [Link](new ArrayList<>(current))


You added a reference. When you backtrack, current changes, corrupting all saved states.

Bug 2 — Incomplete undo: forgetting to restore state after recursion.


The undo step must mirror the choose step exactly.

If your solution gives wrong or duplicate results, check these two things first.

Next up is Chapter 11 — Strings! 🚀

You might also like