Backtracking Concept and Decision Tree Visualization in DSA Go - Time & Space Complexity
Backtracking explores many possible choices to find solutions. We want to understand how the time needed grows as the problem size increases.
How does the number of decisions and paths affect the total work done?
Analyze the time complexity of the following backtracking code snippet.
func backtrack(path []int, choices []int) {
if len(path) == len(choices) {
// solution found
return
}
for _, choice := range choices {
if !contains(path, choice) {
backtrack(append(path, choice), choices)
}
}
}
func contains(slice []int, val int) bool {
for _, v := range slice {
if v == val {
return true
}
}
return false
}
This code tries all possible orderings of choices without repeats, exploring each path fully.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The recursive calls that try each unused choice at every step.
- How many times: For n choices, it tries n options first, then n-1, then n-2, and so on, exploring all permutations.
Each added choice multiplies the number of paths to explore.
| Input Size (n) | Approx. Operations |
|---|---|
| 3 | 6 (3 x 2 x 1) |
| 5 | 120 (5 x 4 x 3 x 2 x 1) |
| 7 | 5040 (7 x 6 x 5 x 4 x 3 x 2 x 1) |
Pattern observation: The operations grow very fast, multiplying by the next smaller number each time (factorial growth).
Time Complexity: O(n!)
This means the time needed grows extremely fast as the number of choices increases, because it tries every possible order.
[X] Wrong: "Backtracking only takes linear time because it just loops over choices."
[OK] Correct: Backtracking explores all possible sequences, which multiply quickly, not just a simple loop.
Understanding backtracking time helps you explain why some problems are hard and how to reason about solution speed. This skill shows you can analyze complex recursive patterns clearly.
"What if we added pruning to skip some paths early? How would the time complexity change?"
Practice
backtracking in algorithms?Solution
Step 1: Understand backtracking approach
Backtracking tries all possible choices step-by-step and goes back if a choice fails.Step 2: Compare with other options
Sorting, loops, and queues are different concepts not related to undoing choices.Final Answer:
Try all possible options and undo choices when they lead to dead ends -> Option BQuick Check:
Backtracking = Try and undo choices [OK]
- Confusing backtracking with sorting or looping
- Thinking backtracking only uses loops
- Ignoring the undo step in backtracking
Solution
Step 1: Check parameter types for backtracking
Backtracking usually passes current path (slice of ints) and available choices (slice of ints).Step 2: Validate function signature
func backtrack(path []int, choices []int) { } matches expected signature with slices for path and choices.Final Answer:
func backtrack(path []int, choices []int) { } -> Option AQuick Check:
Backtracking uses slices for path and choices [OK]
- Using single int instead of slices for path and choices
- Returning values when not needed
- Using wrong data types like string instead of int
func backtrack(nums []int, start int, path []int) {
fmt.Println(path)
for i := start; i < len(nums); i++ {
backtrack(nums, i+1, append(path, nums[i]))
}
}
func main() {
backtrack([]int{1, 2}, 0, []int{})
}Solution
Step 1: Trace initial call and print
First call prints empty path: []Step 2: Trace recursive calls
From start=0, i=0: append 1 -> print [1], then recurse with i=1: append 2 -> print [1 2]Step 3: Trace next iteration
From start=0, i=1: append 2 -> print [2]Final Answer:
[] [1] [1 2] [2] -> Option DQuick Check:
Subsets printed in order with empty first [OK]
- Ignoring empty subset output
- Mixing order of subsets printed
- Not appending correctly in recursive calls
func backtrack(nums []int, start int, path []int) {
if start == len(nums) {
fmt.Println(path)
return
}
backtrack(nums, start+1, path)
backtrack(nums, start+1, append(path, nums[start]))
}
func main() {
backtrack([]int{1, 2}, 0, []int{})
}Solution
Step 1: Check base case correctness
Base case triggers when start equals length, printing current path.Step 2: Check recursive calls
Two calls: one excludes current element, one includes it by appending.Step 3: Confirm no slice mutation issues
append creates new slice, so original path is safe.Final Answer:
No error; code correctly prints all subsets -> Option CQuick Check:
Code prints all subsets correctly [OK]
- Thinking append modifies original slice in place
- Missing base case causing infinite recursion
- Incorrect start increment causing wrong subsets
Solution
Step 1: Understand pruning in backtracking
Pruning means cutting off branches that cannot lead to a solution early.Step 2: Apply to N-Queens problem
Branches where queens attack each other are invalid and should be stopped.Final Answer:
Stopping exploration of branches where queens attack each other -> Option AQuick Check:
Pruning = stop invalid branches early [OK]
- Trying all placements without pruning
- Ignoring conflicts between queens
- Restarting search unnecessarily
Ready to prove it?
Pass the test and this pattern moves unknown
