Facebook Pixel

2518. Number of Great Partitions

Problem Description

You have an array nums containing positive integers and an integer k. Your task is to split this array into two groups where:

  1. Every element must belong to exactly one group - you can't leave any element out or put it in both groups
  2. The order of elements within each group must be preserved - if element at index i comes before element at index j in the original array, and both are in the same group, then element i must come before element j in that group
  3. Both groups must have a sum ≥ k - this is what makes a partition "great"

You need to count how many different ways you can create such great partitions. Two partitions are considered different if at least one element is placed in a different group between them.

For example, if nums = [1, 2, 3] and k = 3:

  • Partition {[1, 2], [3]} is great because sum of first group is 1+2=3 ≥ 3 and sum of second group is 3 ≥ 3
  • Partition {[1], [2, 3]} is NOT great because sum of first group is 1 < 3

The answer should be returned modulo 10^9 + 7 since the number of partitions can be very large.

The solution uses dynamic programming to count partitions where one group has sum less than k, then subtracts these invalid cases from the total possible partitions (2^n where n is the array length). The key insight is that if the total sum is less than 2*k, no great partition is possible since both groups need sum ≥ k.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The first observation is that if the total sum of all elements is less than 2*k, it's impossible to create any great partition since we need both groups to have sum at least k. This gives us our base case.

Now, for counting valid partitions, we could try to directly count partitions where both groups have sum ≥ k, but this is complex. Instead, let's think inversely - what if we count the invalid partitions and subtract them from the total?

Each element can go into either group 1 or group 2, giving us 2^n total possible partitions. Among these, the invalid ones are those where at least one group has sum < k.

Here's the key insight: if group 1 has sum < k, then we can use dynamic programming to count how many ways we can select elements to form group 1 with sum in range [0, k-1]. Similarly for group 2.

We can use a DP table f[i][j] where:

  • i represents considering the first i elements
  • j represents achieving a sum of exactly j
  • f[i][j] counts the number of ways to select elements from the first i elements to get sum j

The DP transition is classic subset sum:

  • Either we don't include element i: f[i][j] = f[i-1][j]
  • Or we include element i (if j >= nums[i-1]): f[i][j] += f[i-1][j-nums[i-1]]

After building the DP table, sum(f[n][0] to f[n][k-1]) gives us the number of ways to form a group with sum < k. Since either group 1 or group 2 could be the one with sum < k, we multiply by 2. But wait - we're double counting the cases where BOTH groups have sum < k, so we need to be careful with our subtraction.

The final answer becomes: total_partitions - 2 * (partitions_with_one_group_sum_less_than_k).

Learn more about Dynamic Programming patterns.

Solution Approach

Let's walk through the implementation step by step:

Step 1: Check if a great partition is possible

if sum(nums) < k * 2:
    return 0

If the total sum is less than 2*k, it's impossible to have both groups with sum ≥ k, so we return 0.

Step 2: Initialize variables and DP table

mod = 10**9 + 7
n = len(nums)
f = [[0] * k for _ in range(n + 1)]
f[0][0] = 1
ans = 1
  • We create a 2D DP table f with dimensions (n+1) × k
  • f[i][j] represents the number of ways to select elements from the first i elements to achieve sum exactly j
  • f[0][0] = 1 because there's one way to get sum 0 with 0 elements (select nothing)
  • ans starts at 1 and will track the total number of partitions (2^n)

Step 3: Fill the DP table using subset sum pattern

for i in range(1, n + 1):
    ans = ans * 2 % mod
    for j in range(k):
        f[i][j] = f[i - 1][j]
        if j >= nums[i - 1]:
            f[i][j] = (f[i][j] + f[i - 1][j - nums[i - 1]]) % mod

For each element nums[i-1]:

  • Update ans by multiplying by 2 (each element can go to either group)
  • For each possible sum j from 0 to k-1:
    • First, copy the count without including current element: f[i][j] = f[i-1][j]
    • If including current element is valid (j >= nums[i-1]), add the count: f[i][j] += f[i-1][j-nums[i-1]]

Step 4: Calculate the final answer

return (ans - sum(f[-1]) * 2 + mod) % mod
  • sum(f[-1]) gives the total number of ways to form a subset with sum in range [0, k-1]
  • These represent the ways to form group 1 with sum < k
  • Since either group could have sum < k, we multiply by 2
  • Subtract from total partitions: ans - sum(f[-1]) * 2
  • Add mod before taking modulo to handle potential negative values

The clever part is that we only track sums up to k-1 in our DP table since we only care about counting invalid partitions (those with sum < k). This optimization keeps space complexity at O(n*k) instead of tracking all possible sums.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example with nums = [1, 2, 3, 4] and k = 5.

Step 1: Check feasibility

  • Total sum = 1 + 2 + 3 + 4 = 10
  • Since 10 ≥ 2*5, great partitions are possible

Step 2: Build DP table to count invalid partitions

We'll build a DP table f[i][j] where rows represent considering first i elements and columns represent achieving sum j (from 0 to k-1 = 4).

Initial state: f[0][0] = 1 (one way to get sum 0 with 0 elements)

    j: 0  1  2  3  4
i=0:   1  0  0  0  0

Processing element 1 (value = 1):

  • f[1][0] = f[0][0] = 1 (don't include 1)
  • f[1][1] = f[0][1] + f[0][0] = 0 + 1 = 1 (include 1)
  • f[1][2] = f[0][2] = 0, f[1][3] = f[0][3] = 0, f[1][4] = f[0][4] = 0
    j: 0  1  2  3  4
i=1:   1  1  0  0  0

Processing element 2 (value = 2):

  • f[2][0] = f[1][0] = 1
  • f[2][1] = f[1][1] = 1
  • f[2][2] = f[1][2] + f[1][0] = 0 + 1 = 1 (include 2 with sum 0)
  • f[2][3] = f[1][3] + f[1][1] = 0 + 1 = 1 (include 2 with sum 1)
  • f[2][4] = f[1][4] + f[1][2] = 0 + 0 = 0
    j: 0  1  2  3  4
i=2:   1  1  1  1  0

Processing element 3 (value = 3):

  • f[3][0] = f[2][0] = 1
  • f[3][1] = f[2][1] = 1
  • f[3][2] = f[2][2] = 1
  • f[3][3] = f[2][3] + f[2][0] = 1 + 1 = 2 (don't include 3, or include 3 with sum 0)
  • f[3][4] = f[2][4] + f[2][1] = 0 + 1 = 1
    j: 0  1  2  3  4
i=3:   1  1  1  2  1

Processing element 4 (value = 4):

  • f[4][0] = f[3][0] = 1
  • f[4][1] = f[3][1] = 1
  • f[4][2] = f[3][2] = 1
  • f[4][3] = f[3][3] = 2
  • f[4][4] = f[3][4] + f[3][0] = 1 + 1 = 2
    j: 0  1  2  3  4
i=4:   1  1  1  2  2

Step 3: Calculate final answer

  • Total partitions = 2^4 = 16
  • Ways to form a group with sum < 5: sum(f[4]) = 1 + 1 + 1 + 2 + 2 = 7
  • Invalid partitions (at least one group has sum < 5) = 7 * 2 = 14
  • Valid great partitions = 16 - 14 = 2

Verification: The 2 great partitions are:

  1. Group 1: [1, 4] (sum = 5), Group 2: [2, 3] (sum = 5)
  2. Group 1: [2, 3] (sum = 5), Group 2: [1, 4] (sum = 5)

Note: These correspond to the binary selections:

  • [1,0,0,1] → element 1 and 4 go to group 1
  • [0,1,1,0] → element 2 and 3 go to group 1

Both result in groups with sum ≥ 5, confirming our answer of 2 great partitions.

Solution Implementation

1class Solution:
2    def countPartitions(self, nums: List[int], k: int) -> int:
3        # If total sum is less than 2k, no valid partition exists
4        # (both parts need sum >= k)
5        total_sum = sum(nums)
6        if total_sum < k * 2:
7            return 0
8      
9        MOD = 10**9 + 7
10        n = len(nums)
11      
12        # dp[i][j] = number of ways to select from first i elements 
13        # to get sum j (where j < k)
14        dp = [[0] * k for _ in range(n + 1)]
15        dp[0][0] = 1  # Base case: one way to get sum 0 with 0 elements
16      
17        # Total number of possible subsets (2^n)
18        total_subsets = 1
19      
20        # Process each element
21        for i in range(1, n + 1):
22            # Update total possible subsets
23            total_subsets = (total_subsets * 2) % MOD
24          
25            # Update DP table for current element
26            for j in range(k):
27                # Option 1: Don't include current element
28                dp[i][j] = dp[i - 1][j]
29              
30                # Option 2: Include current element (if possible)
31                if j >= nums[i - 1]:
32                    dp[i][j] = (dp[i][j] + dp[i - 1][j - nums[i - 1]]) % MOD
33      
34        # Count invalid partitions: subsets with sum < k
35        # Multiply by 2 because each subset creates two partitions
36        # (subset and its complement)
37        invalid_partitions = sum(dp[-1]) * 2
38      
39        # Valid partitions = Total partitions - Invalid partitions
40        result = (total_subsets - invalid_partitions + MOD) % MOD
41      
42        return result
43
1class Solution {
2    private static final int MOD = (int) 1e9 + 7;
3
4    public int countPartitions(int[] nums, int k) {
5        // Calculate the total sum of all elements
6        long totalSum = 0;
7        for (int num : nums) {
8            totalSum += num;
9        }
10      
11        // If total sum is less than 2k, no valid partition exists
12        // Both partitions need sum >= k, so total must be >= 2k
13        if (totalSum < k * 2) {
14            return 0;
15        }
16      
17        int n = nums.length;
18      
19        // dp[i][j] represents the number of ways to select elements from first i elements
20        // such that their sum equals j
21        long[][] dp = new long[n + 1][k];
22        dp[0][0] = 1; // Base case: one way to get sum 0 with 0 elements
23      
24        // Total number of possible partitions (2^n)
25        long totalPartitions = 1;
26      
27        // Process each element
28        for (int i = 1; i <= n; i++) {
29            int currentNum = nums[i - 1];
30          
31            // Each element can go to either partition, so multiply by 2
32            totalPartitions = totalPartitions * 2 % MOD;
33          
34            // Update dp table for all possible sums less than k
35            for (int sum = 0; sum < k; sum++) {
36                // Case 1: Don't include current element in this subset
37                dp[i][sum] = dp[i - 1][sum];
38              
39                // Case 2: Include current element if possible
40                if (sum >= currentNum) {
41                    dp[i][sum] = (dp[i][sum] + dp[i - 1][sum - currentNum]) % MOD;
42                }
43            }
44        }
45      
46        // Subtract invalid partitions where at least one partition has sum < k
47        // dp[n][j] counts subsets with sum j < k
48        // Each such subset creates 2 invalid partitions (subset and its complement)
49        for (int sum = 0; sum < k; sum++) {
50            totalPartitions = (totalPartitions - dp[n][sum] * 2 % MOD + MOD) % MOD;
51        }
52      
53        return (int) totalPartitions;
54    }
55}
56
1class Solution {
2public:
3    const int MOD = 1e9 + 7;
4
5    int countPartitions(vector<int>& nums, int k) {
6        // Calculate total sum of all elements
7        long totalSum = accumulate(nums.begin(), nums.end(), 0L);
8      
9        // If total sum < 2*k, impossible to have both partitions with sum >= k
10        if (totalSum < 2L * k) {
11            return 0;
12        }
13      
14        int n = nums.size();
15      
16        // dp[i][j] = number of ways to select elements from first i elements 
17        // such that their sum equals j
18        long dp[n + 1][k];
19        memset(dp, 0, sizeof(dp));
20      
21        // Base case: empty subset has sum 0
22        dp[0][0] = 1;
23      
24        // Total number of possible partitions (2^n)
25        int totalPartitions = 1;
26      
27        // Build DP table
28        for (int i = 1; i <= n; ++i) {
29            int currentValue = nums[i - 1];
30          
31            // Update total partitions count (multiply by 2 for each element)
32            totalPartitions = (totalPartitions * 2) % MOD;
33          
34            // For each possible sum j
35            for (int j = 0; j < k; ++j) {
36                // Option 1: Don't include current element
37                dp[i][j] = dp[i - 1][j];
38              
39                // Option 2: Include current element (if possible)
40                if (j >= currentValue) {
41                    dp[i][j] = (dp[i][j] + dp[i - 1][j - currentValue]) % MOD;
42                }
43            }
44        }
45      
46        // Subtract invalid partitions where one subset has sum < k
47        // We multiply by 2 because if subset A has sum < k, then either
48        // A is the first partition or the second partition
49        int result = totalPartitions;
50        for (int j = 0; j < k; ++j) {
51            result = (result - dp[n][j] * 2 % MOD + MOD) % MOD;
52        }
53      
54        return result;
55    }
56};
57
1const MOD = 1e9 + 7;
2
3function countPartitions(nums: number[], k: number): number {
4    // Calculate total sum of all elements
5    let totalSum: number = nums.reduce((acc, val) => acc + val, 0);
6  
7    // If total sum < 2*k, impossible to have both partitions with sum >= k
8    if (totalSum < 2 * k) {
9        return 0;
10    }
11  
12    const n = nums.length;
13  
14    // dp[i][j] = number of ways to select elements from first i elements 
15    // such that their sum equals j
16    const dp: number[][] = Array(n + 1).fill(0).map(() => Array(k).fill(0));
17  
18    // Base case: empty subset has sum 0
19    dp[0][0] = 1;
20  
21    // Total number of possible partitions (2^n)
22    let totalPartitions = 1;
23  
24    // Build DP table
25    for (let i = 1; i <= n; i++) {
26        const currentValue = nums[i - 1];
27      
28        // Update total partitions count (multiply by 2 for each element)
29        totalPartitions = (totalPartitions * 2) % MOD;
30      
31        // For each possible sum j
32        for (let j = 0; j < k; j++) {
33            // Option 1: Don't include current element
34            dp[i][j] = dp[i - 1][j];
35          
36            // Option 2: Include current element (if possible)
37            if (j >= currentValue) {
38                dp[i][j] = (dp[i][j] + dp[i - 1][j - currentValue]) % MOD;
39            }
40        }
41    }
42  
43    // Subtract invalid partitions where one subset has sum < k
44    // We multiply by 2 because if subset A has sum < k, then either
45    // A is the first partition or the second partition
46    let result = totalPartitions;
47    for (let j = 0; j < k; j++) {
48        result = (result - dp[n][j] * 2 % MOD + MOD) % MOD;
49    }
50  
51    return result;
52}
53

Time and Space Complexity

Time Complexity: O(n * k)

The algorithm uses dynamic programming with nested loops:

  • The outer loop iterates through all n elements in the array
  • The inner loop iterates through values from 0 to k-1
  • Inside the nested loops, all operations (array access, addition, modulo) are O(1)
  • Additionally, calculating sum(nums) at the beginning takes O(n) time
  • Computing sum(f[-1]) at the end takes O(k) time

Therefore, the overall time complexity is O(n) + O(n * k) + O(k) = O(n * k)

Space Complexity: O(n * k)

The algorithm uses a 2D DP table f with dimensions (n+1) × k:

  • The table f has n+1 rows and k columns, requiring O((n+1) * k) = O(n * k) space
  • Other variables (mod, ans, loop variables) use O(1) space

Therefore, the overall space complexity is O(n * k)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Forgetting to Handle Negative Results in Modular Arithmetic

The Pitfall: When calculating (total_subsets - invalid_partitions) % MOD, if invalid_partitions > total_subsets, the result becomes negative. In Python, this isn't usually a problem since Python handles negative modulo correctly, but in many other languages (like Java or C++), this would give incorrect results. Additionally, even in Python, it's better practice to ensure non-negative intermediate results.

Why It Happens: The counting logic might seem counterintuitive - we're subtracting sum(dp[-1]) * 2 from 2^n. While mathematically this should always be non-negative for valid inputs, during intermediate calculations or edge cases, the subtraction could temporarily produce negative values.

The Solution: Always add MOD before taking the final modulo to ensure a positive result:

result = (total_subsets - invalid_partitions % MOD + MOD) % MOD

Or even better, ensure all intermediate calculations are properly modded:

invalid_partitions = (sum(dp[-1]) * 2) % MOD
result = (total_subsets - invalid_partitions + MOD) % MOD

2. Incorrectly Counting Invalid Partitions

The Pitfall: A common mistake is forgetting to multiply sum(dp[-1]) by 2. The reasoning is subtle: when we find a subset with sum < k, this creates an invalid partition where either the subset OR its complement could be "group 1". Both cases are invalid and need to be counted.

Example of the Error:

# WRONG: Only counting one side
invalid_partitions = sum(dp[-1])  # Missing multiplication by 2

Why It's Wrong: If subset A has sum < k, then partition (A, B) is invalid. But partition (B, A) where B is the complement might also be invalid if B's sum < k. We need to count both possibilities.

3. Off-by-One Error in DP Array Indexing

The Pitfall: When accessing nums[i-1] in the DP loop, it's easy to accidentally use nums[i] instead, causing an index out of bounds error or incorrect calculations.

The Problem Code:

# WRONG: Using wrong index
if j >= nums[i]:  # Should be nums[i-1]
    dp[i][j] = (dp[i][j] + dp[i-1][j - nums[i]]) % MOD

The Correct Version:

if j >= nums[i - 1]:
    dp[i][j] = (dp[i][j] + dp[i - 1][j - nums[i - 1]]) % MOD

This happens because the DP table has dimensions (n+1) × k where i ranges from 0 to n, but the nums array has indices from 0 to n-1.

4. Not Checking for Edge Case Where No Valid Partition Exists

The Pitfall: Forgetting the initial check if total_sum < k * 2: return 0 would lead to incorrect counting. The DP would still run, but the mathematical logic breaks down when it's impossible to have both groups with sum ≥ k.

Why It Matters: Without this check, the algorithm might return a positive count even when no valid partition exists, because the subtraction logic assumes at least the possibility of valid partitions existing.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What are the two properties the problem needs to have for dynamic programming to be applicable? (Select 2)


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More