Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Analyzing Backtracking Complexity: Subsets, Combinations, and Permutations

Hello! Over the past few lessons, we've built a solid foundation in backtracking by implementing algorithms to generate subsets, combinations, and permutations. We've focused on the "how"—the code, the recursion, and handling tricky cases like duplicates. Now, we'll shift our focus to the "what"—what is the performance cost of these algorithms?

This lesson will equip you to estimate the time complexity of backtracking solutions for subsets, combinations, and permutations. This is a critical skill for coding interviews, where you're often asked to analyze your own solution. We'll develop a practical framework for this estimation, focusing on intuition and the structure of the recursion tree, rather than complex mathematical proofs, which aligns with your goal of gaining a deep but practical understanding.

A General Framework for Analyzing Backtracking

At its core, a backtracking algorithm explores a tree of choices. To estimate its time complexity, we need to understand two things: the size of this tree and the amount of work we do at each step.

A good rule of thumb for estimating the time complexity is:

Time Complexity ≈ (Number of states/nodes in the recursion tree) × (Work done per state/node)

For many problems, the total number of leaf nodes in the tree corresponds to the number of valid solutions. The work done to generate the final list of solutions often dominates the complexity. A more refined estimate is:

Time Complexity ≈ (Number of valid solutions) × (Work to build one solution) + (Work in all non-solution nodes)

Let's apply this framework to the problems we've studied.

Case 1: Subsets (Power Set)

Recall the "subsets" problem where we generate all possible subsets of a given set [1, 2, 3]. The fundamental choice for each element is binary: either we include it in the current subset, or we exclude it.

  • Recursion Tree: For an input of size , this creates a binary decision tree of depth .
  • Number of Solutions (Leaves): Since each of the elements can either be in or out of a subset, there are possible subsets. These are the leaf nodes of our conceptual decision tree.
  • Work per Solution: When our backtracking function reaches a leaf (a complete subset), we typically copy the temporary list holding the current subset into our final results list. The maximum length of a subset is , so this copy operation takes up to time.

Putting It Together:
The total work is dominated by generating and storing all the subsets. We have subsets in total. While some are short (like the empty set) and some are long (the set itself), the total number of elements across all subsets is . Therefore, the time complexity to generate all results is .

The following resource provides a great walkthrough of this estimation.

Backtracking | Interviews.school

This section from Interviews.school breaks down the time and space complexity for the subsets problem. It offers a practical way to arrive at the upper bound without getting lost in formal proofs.

Focus on the text starting from the complexity analysis. The author shows how to estimate the number of states and the work done to arrive at the O(2^N \times N) runtime. Also, read the section on space complexity that follows, which correctly identifies that storing the result is the dominant factor.

The NeetCode video on subsets also touches upon this, reinforcing the final complexity.

Subsets - Backtracking - Leetcode 78

This is the video on generating subsets we have seen before. Here, we'll just re-watch a short clip where the complexity is discussed.

Pay attention to the segment from this timestamp, where the presenter explains why the complexity must be at least O(n \cdot 2^n) simply due to the size of the required output.

Space Complexity: The space complexity is also dominated by storing the output, which is . The recursion stack depth contributes an additional , but this is overshadowed by the output size.

Case 2: Permutations

For permutations of a set of size , the choice structure is different.

  • Recursion Tree: At the first level of recursion, we have choices for the first element. At the second level, we have choices, and so on, until we have only one choice for the last element.
  • Number of Solutions (Leaves): The total number of permutations is . These are the leaf nodes of our recursion tree.
  • Work per Solution: Each time we form a complete permutation of length , we copy it to our results list. This is an operation.

Putting It Together:
Since we generate solutions and each requires an copy, the time complexity is . The number of internal nodes in the permutation recursion tree is also on the order of , so this estimate holds.

The following video provides an excellent visual and verbal explanation of where this complexity comes from.

Permutations (LeetCode 46) | Full solution with backtracking examples | Interview | Study Algorithms

This video by Nikhil Lohia gives a clear, step-by-step construction of the permutation decision tree and then analyzes its complexity.

First, watch the conceptual part from the logic breakdown, where the presenter calculates the total number of permutations and draws the initial decision tree. This helps visualize the n! growth. Then, skip to the complexity analysis at the end, where the O(n \cdot n!) time complexity is explicitly stated and justified.

Space Complexity: Similar to subsets, the space is dominated by the output, which requires storing permutations of length , leading to space. The recursion stack adds .

Case 3: Combinations (n choose k)

Finally, let's consider generating all combinations of size k from a set of n elements (the "n choose k" problem).

  • Recursion Tree: This tree is pruned compared to the subsets tree. The recursion stops once our current combination reaches size k. The depth of the recursion is k.
  • Number of Solutions (Leaves): The number of ways to choose k elements from a set of n is given by the binomial coefficient, . This is the number of leaf nodes.
  • Work per Solution: Each time we form a valid combination of size k, we perform an copy operation.

Putting It Together:
The time complexity is the number of solutions multiplied by the work to copy each one: .

This is cleanly summarized in the following reading.

Backtracking Introduction | DSA

This section from AlgoMaster.io provides a concise table summarizing the typical complexities of backtracking patterns.

Focus on the table under the heading Time Complexity Patterns. Note the entries for Subsets, Permutations, and especially Combinations, which is listed as O(C(n,k) \cdot k). The explanation "Number of combinations times copy cost" is exactly the logic we just used.

Space Complexity: Storing the output requires space. The recursion stack depth is or depending on the specific implementation.

A Visual Summary and the Power of Pruning

The "Backtracking Patterns Cheat Sheet" below provides a great visual summary of the recursion trees for these problems.

This cheat sheet illustrates the recursion trees and resulting time complexities for four common backtracking patterns.

Let's interpret this sheet in light of what we've learned:

  • Subset Pattern: Shows the "pick/not pick" tree, leading to solutions. As we analyzed, the runtime including output is . The sheet simplifies this, which is common.
  • Permutation Pattern: Shows the tree with a decreasing number of branches (), correctly identifying the nature.
  • Combination Pattern: This depicts generating combinations by iterating through choices ([1], then [1,2], [1,3]). If this process is used to generate combinations of all possible sizes, it becomes equivalent to the subsets problem, hence the complexity listed. This is different from the specific "n choose k" problem we analyzed. It's a good reminder that the problem definition is key!
  • Combination Sum Pattern: This hints at a more advanced type of problem. The key takeaway is the concept of pruning. A backtracking algorithm's actual performance is often better than the theoretical worst-case because invalid paths are abandoned early.

The image below shows a pruned search tree for a Combination Sum problem. Notice how the red arrows indicate branches that were "pruned" or abandoned because they could not possibly lead to a solution. This is the essence of the "constraint" in the backtracking framework (choices, constraints, goal) and is what makes backtracking more efficient than brute-forcing every single possibility.

A search tree for a combination sum problem. Red edges show pruned branches, where the algorithm stopped exploring a path because it exceeded the target. Green nodes are valid solutions.

Conclusion

In this lesson, we transitioned from implementing backtracking algorithms to analyzing their performance. This is the final step in truly mastering an algorithmic pattern.

Key Takeaways:

  • General Estimation Strategy: The time complexity of a backtracking algorithm is strongly related to the number of solutions and the cost of building each one.
  • Subsets (Power Set): With elements, you have subsets. The time complexity is due to copying results.
  • Permutations: You have permutations. The time complexity is .
  • Combinations (n choose k): You have combinations. The time complexity is .
  • Complexity Classes: Notice the hierarchy of growth: can be much smaller than , which in turn is much smaller than for larger .

This lesson concludes our module on backtracking. You are now equipped not only to implement these core patterns but also to analyze their efficiency confidently.

In the next module, we will explore a new data structure: the Trie, or prefix tree. We will see how it can be used to solve string-related problems efficiently, and interestingly, how it can be combined with backtracking to dramatically prune the search space in problems like finding words on a grid.

Can't find a good explanation? Sign up and we'll make it for you

Sign up