5 Best Ways to Program to Find Number of Consecutive Subsequences Whose Sum is Divisible by K in Python

Exploring Python Methods to Find Subsequences Divisible by K

πŸ’‘ Problem Formulation: Given an array of integers and an integer K, the objective is to find the number of consecutive subsequences within the array whose sum is divisible by K. For example, for the array [1, 2, 3, 4] and K = 3, there are three subsequences whose sum is divisible by K: [3], [1, 2], and [1, 2, 3].

Method 1: Brute Force Approach

This method uses a double loop to iterate through all possible subsequences of the array, calculating the sum and checking divisibility by K. It is straightforward but may be inefficient for large arrays due to its O(n^2) time complexity.

Here’s an example:

def count_subsequences(arr, k):
    count = 0
    for i in range(len(arr)):
        total = 0
        for j in range(i, len(arr)):
            total += arr[j]
            if total % k == 0:
                count += 1
    return count

# Example usage
print(count_subsequences([1, 2, 3, 4], 3))

Output: 3

This code snippet defines a function that counts the number of subsequences with a sum divisible by K by iterating over all possible start and end indices in the sequence and summing the values in between. It increments a counter each time the sum is divisible by K.

Method 2: Prefix Sum with Modulo Hashing

Prefix sum with modulo hashing utilizes a hash map to store counts of prefix sums modulo K. The method is more efficient than brute force, especially for large datasets, with a time complexity of O(n).

Here’s an example:

from collections import defaultdict

def count_subsequences(arr, k):
    count = 0
    total = 0
    prefix_sums = defaultdict(int)
    prefix_sums[0] = 1

    for num in arr:
        total = (total + num) % k
        count += prefix_sums[total]
        prefix_sums[total] += 1

    return count

# Example usage
print(count_subsequences([1, 2, 3, 4], 3))

Output: 3

In this code snippet, the function keeps track of the cumulative sum modulo K and uses a dictionary to count how many times each modulo value has appeared. This helps to determine the number of subsequences ending at the current index that are divisible by K.

Method 3: Dynamic Programming

Dynamic programming can be used to solve this problem by maintaining a running total and a count of how often each remainder appears. The total complexity is still O(n), but it avoids the overhead of modulo operations and dictionary lookups used in the previous method.

Here’s an example:

def count_subsequences(arr, k):
    dp = [0] * k
    dp[0] = 1
    count = 0
    total = 0

    for num in arr:
        total += num
        index = total % k
        count += dp[index]
        dp[index] += 1

    return count

# Example usage
print(count_subsequences([1, 2, 3, 4], 3))

Output: 3

This code snippet implements a dynamic array that keeps track of the number of times a particular sum modulo K has been seen. Rather than using a hash map, it uses a list indexed by the sum modulo K.

Method 4: Cumulative Sum with Early Termination

This approach is a variation of the brute force method that includes an early exit condition. While maintaining a running total, the algorithm can skip certain iterations when the running total minus the current value still satisfies divisibility conditions, slightly improving efficiency.

Here’s an example:

def count_subsequences(arr, k):
    count = 0
    for i in range(len(arr)):
        total = 0
        for j in range(i, len(arr)):
            total += arr[j]
            if total % k == 0:
                count += 1
                break
    return count

# Example usage
print(count_subsequences([1, 2, 3, 4], 3))

Output: 3

The early termination condition in this code snippet helps to minimize the number of inner loop iterations by breaking out of the loop once a valid subsequence is found, without continuing to check longer subsequences that start with the same index.

Bonus One-Liner Method 5: Functional Programming Approach

This one-liner approach leverages functional programming style in Python, utilizing list comprehensions and built-in functions for a concise solution. However, this method is similar to brute force in efficiency, and its compactness may sacrifice readability.

Here’s an example:

def count_subsequences(arr, k):
    return sum(1 for i in range(len(arr)) for j in range(i+1, len(arr)+1) if sum(arr[i:j]) % k == 0)

# Example usage
print(count_subsequences([1, 2, 3, 4], 3))

Output: 3

The one-liner uses a nested list comprehension to iterate over all possible subsequences and the built-in sum function to check if the subsequence sum is divisible by K. It does this check in a condensed way.

Summary/Discussion

  • Method 1: Brute Force Approach. Simple to implement. High time complexity, making it unsuitable for large datasets.
  • Method 2: Prefix Sum with Modulo Hashing. Efficient time complexity. More complex logic than brute force, but significantly faster for large inputs.
  • Method 3: Dynamic Programming. Balanced approach with efficient use of space and time. May require a deeper understanding of DP concepts.
  • Method 4: Cumulative Sum with Early Termination. Incorporates early exits to slightly improve the efficiency of brute force. Still not ideal for large datasets.
  • Bonus One-Liner Method 5: Functional Programming Approach. Concise and elegant, but may be hard to understand for newcomers. Equally inefficient as brute force for large arrays.