Recursive Backtracking - Permutations
Dice Rolls
To generate all possible sequences of values.
for (each possible first die value): for (each possible second die value): for (each possible third die value): … print or keep in some Data Structure!
– This is called a depth-first search
public void diceRolls(int n){
List<Integer> chosen = new ArrayList<>();
diceRollHelper(n, chosen);
}
private static void diceRollHelper(int dice, List<Integer> chosen) {
if (dice == 0) {
System.out.println(formatChosen(chosen));
} else {
for (int i = 1; i <= 6; i++) {
chosen.add(i); // choose
diceRollHelper(dice - 1, chosen); // explore
chosen.remove(chosen.size() - 1); // un-choose
}
}
}
Returns all possible dice roll combinations as a list of lists
public List<List<Integer>> diceRolls(int n){
List<List<Integer>> result = new ArrayList<>();
List<Integer> chosen = new ArrayList<>();
diceRollHelper(n, chosen, result);
return result;
}
private static void diceRollHelper(int dice, List<Integer> chosen, List<List<Integer>> result) {
if (dice == 0) {
result.add(new ArrayList<>(chosen)); // base case: add a copy
} else {
for (int i = 1; i <= 6; i++) {
chosen.add(i); // choose
diceRollHelper(dice - 1, chosen, result); // explore
chosen.remove(chosen.size() - 1); // un-choose
}
}
}
Dice Roll Sum
It also accepts a desired sum and prints only combinations that add up to exactly that sum.
public void diceSum(int numDice, int sum){
List<Integer> chosen = new ArrayList<>();
helper(numDice, sum, chosen);
}
private void helper(int numDice, int sum, List<Integer> chosen){
if(numDice == 0){
if(sumAll(chosen) == sum){//Printing only the combinations that add up to exactly the desired sum
System.out.println(printAll(chosen));
}
return;
}
for(int i=1; i<=6; i++){//6 types of choices
//choose
chosen.add(i);
//explore
helper(numDice-1,sum,chosen);
//unchoose
chosen.remove(chosen.size()-1);
}
return;
}
private int sumAll(List<Integer> chosen){
return chosen.stream().mapToInt(Integer::intValue).sum();
}
Optimized with pruning
public void diceSum(int numDice, int target){
List<Integer> chosen = new ArrayList<>();
helper(numDice, 0,target, chosen);
}
private void helper(int numDice, int sum, int target, List<Integer> chosen){
if(numDice == 0){
if(sum==target){
System.out.println(printAll(chosen));
return;
}
return;
} else if(sum+(1*numDice) <= target && sum+(6*numDice) >= target){
for(int i=1; i<=6; i++){//6 types of choices
//choose
chosen.add(i);
//explore
helper(numDice-1,sum+i,target,chosen);
//unchoose
chosen.remove(chosen.size()-1);
}
}
return;
}
Permutations and Arrangements
A permutation is a rearrangement of the elements of a sequence
Decision at each step (each level of the tree):
- What is the next element going to get added to the permutation?
Options at each decision (branches from each node):
- One option for every remaining element that hasn’t been selected yet
- Note: The number of options will be different at each level of the tree!
Information we need to store along the way:
- The permutation you’ve built so far
- The remaining elements in the original sequence
![]()
Problem 3.1: Generate All Permutations
Problem: Given [1, 2, 3], generate all possible arrangements.
Output: [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]
Key Difference from Subsets:
- Subsets: Each element included or excluded (2 choices)
- Permutations: Each position can be ANY unused element (n choices for 1st, n-1 for 2nd, etc.)
Think: We need to track which elements we’ve already used.
Visual Decision Tree for [1, 2]:
[]
┌──────────────┴──────────────┐
pick 1 pick 2
[1] [2]
│ │
pick 2 pick 1
[1,2] [2,1]
Inefficient but intuitive
public void permute(String str){
helper(str,"");
}
private void helper(String remaining, String sofar){
if(remaining.isEmpty()){
System.out.println(sofar);
} else {
for(int i = 0; i < remaining.length(); i++){
char next = remaining.charAt(i);
String rest = remaining.substring(0,i) + remaining.substring(i+1);//choose
helper(rest,sofar+next);//explore
}
}
}