Post

Recursion Pattern : To Take or Don't Take

Recursion Pattern : To Take or Don't Take

Here we will be discussing a recursive template for solving problems related to subsequence generation or power set generation. Generally speaking, this is a variant of the backtracking pattern, where we generate a subsequence out of the input sequence by iterating through each index and deciding whether to “Take” or “Don’t Take” the element at that index.

By definition, a subsequence of a list (or an array) is a sequence that can be derived from another sequence by removing zero or more elements. Thus, we have ( 2^n ) subsequences for a list of ( n ) elements.

For example, the list [1, 2, 3] has the following subsequences:

  • [], [1], [2], [3], [1, 2], [1, 3], [2, 3], [1, 2, 3]

Let’s dive into this pattern by looking at a problem where we generate all possible subsequences of an array using recursion. The approach is straightforward: each recursive call will “process” the element at a specific index in the array, and then the recursion moves to the next index terminating once we reach end of the array. At each level, we will make two recursive calls for the next level—one where we “Take” the element, and another where we “Don’t Take” the element.

Example: Recursion Tree for Finding Subsequences of [1, 2, 3]

Here’s the recursion tree and code for generating all possible subsequences of the array [1, 2, 3]:

Recursion Tree for subsequence generation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def genSubSequence(source: list, subsequence: list,
                   index: int) -> None:
    # Base case: if we've processed all elements,
    # print the current subsequence
    if index >= len(source):
        print(subsequence)
        return

    # Take the element at the current index
    subsequence.append(source[index])
    genSubSequence(source, subsequence, index + 1)

    # Backtrack by removing the last added element
    # (don't take the element)
    subsequence.pop()

    # Don't take the element at the current index
    genSubSequence(source, subsequence, index + 1)

# Example call
genSubSequence([1, 2, 3], [], 0)

The time complexity of this algorithm is O(( n*2^n )) and spance complexity is O(n).

Similar Problems

The above code demonstrates a generic pattern that can be varied and applied to various problems. Let’s explore some related problems:


Problem 1: Print/Count All Subsequences of an Array Which Sum to ‘k’

In the above code, instead of passing the subsequence itself, we pass the sum, which gets incremented with an element at an index if we “take” it, or remains unchanged if we “don’t take” it. This allows us to track the sum of the subsequences as we generate them.


Problem 2: Combination Sum

This problem is slightly different from Problem 1 as each element can be picked multiple times. We follow a similar approach to the one in Problem 1, but with a slight modification: when we “take” an element at index i, we do not recurse by incrementing the index. Instead, we reuse the same index, which allows the same element to be picked multiple times.

Here is the code for this approach:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def combinationSumI(source: list, targetSum: int,
                    combination: list, index: int) -> None:
    # Calculate the current sum of the combination
    currSum = sum(combination)

    # Base case: if index is out of range or current sum
    # exceeds target, return
    if index >= len(source) or currSum > targetSum:
        return

    # If we reach the target sum, print the combination
    if currSum == targetSum:
        print(combination)
        return

    # Include the current element and recurse
    combination.append(source[index])
    combinationSumI(source, targetSum, combination, index)

    # Backtrack: remove the last element
    combination.pop()

    # Don't take the element at the current index and move
    # to the next index
    combinationSumI(source, targetSum, combination,
                    index + 1)

# Example call
combinationSumI([2, 3, 5], 8, [], 0)
This post is licensed under CC BY 4.0 by the author.