5 Best Ways to Find Number of Increasing Subsequences of Size k in Python

πŸ’‘ Problem Formulation: We need to create a program in Python that can find all increasing subsequences of a certain size k in a given list of numbers. For instance, if the input list is [1, 2, 3, 4] and k is 3, the program should return the count 1, since there is only one increasing subsequence of size 3, which is [1, 2, 3].

Method 1: Recursive Approach

This method utilizes a recursive function that explores all possible subsequences of the given array to count the increasing subsequences of size k. The function recursively constructs subsequences and increments the count whenever a valid increasing subsequence of size k is found.

Here’s an example:

def count_increasing_subsequences(arr, k, n, prev, index):
    if k == 0:
        return 1
    if index >= n:
        return 0
    count = 0
    if arr[index] > prev:
        count += count_increasing_subsequences(arr, k-1, n, arr[index], index + 1)
    count += count_increasing_subsequences(arr, k, n, prev, index + 1)
    return count

# Example usage
arr = [10, 15, 8, 25, 16]
k = 3
print(count_increasing_subsequences(arr, k, len(arr), float('-inf'), 0))

Output:

2

This recursive function begins with the entire array and an initial previous value set to negative infinity. It then either includes the current element in the subsequence or skips it, counting how many valid subsequences of size k can be formed. The example shows that in the array [10, 15, 8, 25, 16], there are two increasing subsequences of length 3.

Method 2: Dynamic Programming

Dynamic Programming (DP) can optimize the recursive approach by storing intermediate results. This method leverages a 2D DP array where dp[i][j] stores the count of increasing subsequences of size j ending with the i-th element.

Here’s an example:

def count_increasing_subsequences_DP(arr, k):
    n = len(arr)
    dp = [[0 for _ in range(k+1)] for _ in range(n)]
    for j in range(1, k+1):
        for i in range(n):
            dp[i][j] = sum(dp[m][j-1] for m in range(i) if arr[m] < arr[i])
            if j == 1:
                dp[i][j] = 1
    return sum(dp[i][k] for i in range(n))

# Example usage
arr = [10, 15, 8, 25, 16]
k = 3
print(count_increasing_subsequences_DP(arr, k))

Output:

2

In this approach, we first initialize a DP table to store counts for each subsequence length ending with each element. The example calculates the number of increasing subsequences of size 3 in the array [10, 15, 8, 25, 16], utilizing the DP table for efficient lookups, resulting in 2 valid subsequences.

Method 3: Iterative Using Bitmasking

This method involves generating all possible combinations of array elements using bitwise operations to represent whether an element is included or excluded, then counting the ones that form an increasing subsequence of size k.

Here’s an example:

def count_increasing_subsequences_bitmask(arr, k):
    n = len(arr)
    count = 0
    for bitmask in range(1, 1 << n):
        subseq = [arr[i] for i in range(n) if bitmask & (1 << i)]
        if len(subseq) == k and all(subseq[i] < subseq[i+1] for i in range(k-1)):
            count += 1
    return count

# Example usage
arr = [10, 15, 8, 25, 16]
k = 3
print(count_increasing_subsequences_bitmask(arr, k))

Output:

2

This method uses a bitmask to iterate over all possible combinations of the given array’s elements to check for subsequences of the desired size k. If a subsequence of size k is strictly increasing, it is counted towards the total. The example demonstrates this with an input array of [10, 15, 8, 25, 16] and k = 3, with a count of 2 valid subsequences.

Method 4: Using Combinations from itertools

The itertools library in Python provides a utility to generate all possible combinations of elements. We use the combinations function to generate all subsequences of size k and then count the number of increasing subsequences.

Here’s an example:

from itertools import combinations

def count_increasing_subsequences_itertools(arr, k):
    return sum(1 for combo in combinations(arr, k) if all(combo[i] < combo[i+1] for i in range(k-1)))

# Example usage
arr = [10, 15, 8, 25, 16]
k = 3
print(count_increasing_subsequences_itertools(arr, k))

Output:

2

This snippet uses Python’s built-in itertools library to generate all possible k-sized combinations of the array elements, then counts how many of these are increasing by checking each one. The example demonstrates that the number of increasing subsequences of length 3 in the provided array is 2.

Bonus One-Liner Method 5: Functional Approach with filter and map

This concise method uses Python’s functional programming features such as map and filter to generate all combinations and then filters out the increasing subsequences of size k. It’s a clever and compact one-liner that showcases the power of functional programming in Python.

Here’s an example:

from itertools import combinations

# Example usage
arr = [10, 15, 8, 25, 16]
k = 3
print(sum(map(lambda x: all(map(lambda i, j: i < j, x[:-1], x[1:])), combinations(arr, k))))

Output:

2

This one-liner uses the itertools library to create combinations of the array elements of size k and then applies a lambda function within a filter to test for increasing subsequences. Using map, it compares elements pairwise, and if all comparisons are true (i.e., the sequence is strictly increasing), it contributes to the total count, as shown in the example.

Summary/Discussion

  • Method 1: Recursive Approach. This method explores the problem in depth but can be inefficient for larger arrays due to its exponential complexity.
  • Method 2: Dynamic Programming. The DP method is more efficient than the recursive approach as it avoids redundant calculations, making it suitable for moderately sized arrays.
  • Method 3: Iterative Using Bitmasking. This method is practical for smaller arrays and offers a clear insight into generating all possible subsequences although it can be computationally expensive.
  • Method 4: Using Combinations from itertools. Utilizing built-in Python functionality, this method is elegant and easy to understand but doesn’t scale well for large arrays.
  • Method 5: Functional Approach with filter and map. A compact and elegant solution suitable for smaller arrays and for those who prefer a functional programming style.