Integer Partition Algorithm
Problem
The integers can be partitioned by representing a number as the sum of positive integers (ignoring order). The task is to compute all possible integer partitions of a given number n.
For a number n = 5, the integer partitions are:
54 + 13 + 22 + 2 + 12 + 1 + 1 + 11 + 1 + 1 + 1 + 1
Examples
Example 1
Input:
n = 4
Output:
[[4], [3, 1], [2, 2], [2, 1, 1], [1, 1, 1, 1]]
Explanation:
Partitions of 4 are:
- 4
- 3+1
- 2+2
- 2+1+1
- 1+1+1+1
Example 2
Input:
n = 3
Output:
[[3], [2, 1], [1, 1, 1]]
Explanation:
Partitions of 3 are:
- 3
- 2+1
- 1+1+1
Solution
Method 1 - Recursion
One important observation is that permutations of the summands in a partition do not create new, distinct partitions. For example, both 2+3 and 3+2 represent the same partition of 5. Therefore, to find all unique partitions of an integer N, we need to ensure our algorithm respects this rule.
While generating functions can mathematically solve this problem (refer to Wikipedia’s entry for details), implementing such functions algorithmically is complex. A simpler approach is to use a recursive divide-and-conquer method. This means breaking the problem into smaller subproblems recursively to find all possible partitions.
Divide-and-Conquer Intuition
To find all partitions for a number N:
-
First, split the solutions into two groups:
- Group A: Includes partitions that use the number
Nitself at least once (this group has only one solution:N). - Group B: Partitions that exclude
N, which reduces the problem to finding all partitions ofNusing integers smaller thanN(subset{1, 2, ..., N-1}).
- Group A: Includes partitions that use the number
-
Recursively apply this process to Group B, splitting it into:
- Subgroup A: Solutions that include the next largest number (
N-1). - Subgroup B: Solutions that exclude
N-1.
- Subgroup A: Solutions that include the next largest number (
By using this recursive strategy, we can systematically generate all unique partitions by progressing from the largest number to smaller numbers.
Recursive Base Cases
For the recursive function:
- Sum = 0:
If the target sum is exhausted (
sum = 0), this marks a valid partition. We return 1 for this case since a valid partition is found. - Sum < 0:
If the current sum becomes negative (
sum < 0), the partition is invalid, and we return 0 to signify no solution. - Largest number available = 0: If the largest available number becomes zero, there are no integers left to form the partition, and we return 0.
Algorithm Description and Variables
The recursive function is structured as follows:
- Input 1:
sum- The target sum we are trying to partition. - Input 2:
largestNumber- The largest number allowed in a partition for the current recursive call. - Using these variables, the function dynamically adjusts the range of integers it can use to form partitions, ensuring uniqueness by keeping the numbers in progressively non-increasing order.
Code
C++
class Solution {
public:
int helper(int sum, int largestNumber) {
// Base Cases
if (sum == 0)
return 1; // Found one valid partition
if (sum < 0 || largestNumber == 0)
return 0; // No valid partition
// Divide-and-Conquer: Include the largestNumber OR exclude it
int includeLargest = helper(sum - largestNumber, largestNumber);
int excludeLargest = helper(sum, largestNumber - 1);
return includeLargest + excludeLargest; // Combine both groups
}
int integerPartition(int n) {
return helper(n, n); // Call the helper function
}
};
// Example usage
int main() {
Solution solution;
int n = 5;
cout << "Number of partitions for " << n << ": " << solution.integerPartition(n) << endl;
return 0;
}
Java
class Solution {
private int helper(int sum, int largestNumber) {
// Base cases
if (sum == 0)
return 1; // Found one valid partition
if (sum < 0 || largestNumber == 0)
return 0; // No valid partition
// Divide-and-Conquer: Include the largestNumber OR exclude it
int includeLargest = helper(sum - largestNumber, largestNumber);
int excludeLargest = helper(sum, largestNumber - 1);
return includeLargest + excludeLargest; // Combine both groups
}
public int integerPartition(int n) {
return helper(n, n); // Start the recursive partitioning
}
// Example usage
public static void main(String[] args) {
Solution solution = new Solution();
int n = 5;
System.out.println("Number of partitions for " + n + ": " + solution.integerPartition(n));
}
}
Python
class Solution:
def integerPartition(self, n):
# Recursive function to compute partitions
def helper(sum, largestNumber):
# Base Cases
if sum == 0:
return 1 # Found one valid partition
if sum < 0 or largestNumber == 0:
return 0 # No valid partition
# Divide-and-Conquer: Include the largestNumber OR exclude it
includeLargest = helper(sum - largestNumber, largestNumber)
excludeLargest = helper(sum, largestNumber - 1)
return includeLargest + excludeLargest # Combine both groups
# Call the helper function
return helper(n, n)
# Example usage:
solution = Solution()
n = 5
print(f"Number of partitions for {n}: {solution.integerPartition(n)}")
Complexity
- ⏰ Time complexity:
O(2^n)for givenn. The naive approach splits the problem into two recursive branches at each step (largestNumberbeing included or excluded), resulting in exponential growth. - 🧺 Space complexity:
O(n). Each recursive call consumes stack space. In the worst case (with no base cases reached), the recursion depth equalsn.
Method 2 - Using Top-down DP approach using memoization
Memoization optimises the naive recursive solution by storing previously computed results in a hash map or table. This avoids redundant recursive calls and significantly reduces the time complexity.
Here is the approach:
- Memoization Table:
Use a 2D data structure (
memo[sum][largestNumber]) to store results:sumis the remaining sum we want to partition.largestNumberis the largest number currently allowed in the partition.
- Recursive Computation:
For each
(sum, largestNumber), compute the partitions by:- Including the current largest number.
- Excluding the current largest number.
- Base Cases:
Same as the naive recursive solution:
- If
sum == 0, a valid partition is found → Return1. - If
sum < 0orlargestNumber == 0, no valid partition → Return0.
- If
Code
C++
class Solution {
private:
unordered_map<string, int> memo; // Memoization dictionary
// Private helper function with memoization
int helper(int sum, int largestNumber) {
// Create memoization key
string key = to_string(sum) + "," + to_string(largestNumber);
if (memo.find(key) != memo.end())
return memo[key];
// Base cases
if (sum == 0)
return 1; // Found a valid partition
if (sum < 0 || largestNumber == 0)
return 0; // No valid partition
// Recursive computation with memoization
int includeLargest = helper(sum - largestNumber, largestNumber);
int excludeLargest = helper(sum, largestNumber - 1);
memo[key] = includeLargest + excludeLargest;
return memo[key];
}
public:
// Public method to start partitioning
int integerPartition(int n) {
return helper(n, n); // Start recursion with the full range
}
};
// Example usage
int main() {
Solution solution;
int n = 5;
cout << "Number of partitions for " << n << ": " << solution.integerPartition(n) << endl;
return 0;
}
Java
class Solution {
private Map<String, Integer> memo = new HashMap<>(); // Memoization map
// Private helper function with memoization
private int helper(int sum, int largestNumber) {
// Create memoization key
String key = sum + "," + largestNumber;
if (memo.containsKey(key))
return memo.get(key);
// Base cases
if (sum == 0)
return 1; // Found a valid partition
if (sum < 0 || largestNumber == 0)
return 0; // No valid partition
// Recursive computation with memoization
int includeLargest = helper(sum - largestNumber, largestNumber);
int excludeLargest = helper(sum, largestNumber - 1);
memo.put(key, includeLargest + excludeLargest);
return memo.get(key);
}
// Public method to start partitioning
public int integerPartition(int n) {
return helper(n, n); // Start recursion with the full range
}
// Example usage
public static void main(String[] args) {
Solution solution = new Solution();
int n = 5;
System.out.println("Number of partitions for " + n + ": " + solution.integerPartition(n));
}
}
Python
class Solution:
def integerPartition(self, n):
# Initialize memo dictionary
memo = {}
# Private helper function with memoization
def helper(sum, largestNumber):
# Use memoization key for current subproblem
key = (sum, largestNumber)
if key in memo:
return memo[key]
# Base cases
if sum == 0:
return 1 # Found a valid partition
if sum < 0 or largestNumber == 0:
return 0 # No valid partition
# Recursive computation with memoization
includeLargest = helper(sum - largestNumber, largestNumber)
excludeLargest = helper(sum, largestNumber - 1)
memo[key] = includeLargest + excludeLargest
return memo[key]
return helper(n, n)
# Example usage
solution = Solution()
n = 5
print(f"Number of partitions for {n}: {solution.integerPartition(n)}")
Complexity
- ⏰ Time complexity:
O(n^2). Memoization reduces redundant recursive calls ton x npossible combinations of(sum, largestNumber). - 🧺 Space complexity:
O(n^2)for using the memoization table.
Method 3 - Using bottom-up DP
A bottom-up dynamic programming approach for the integer partition problem involves constructing a table (DP array) where:
- Each column represents the largest number allowed in the partition at that step.
- Each row represents the target sum (
i) we want to partition.
This approach eliminates recursion, focusing on iteratively solving smaller subproblems and combining their solutions to form the final result.
Steps
- DP Table Initialization:
- Set up the table with the base cases:
- When the column (
largest number) is0, there are no valid partitions →table[i][0] = 0. - When the row (
target sum) is0, the sum is already achieved →table[0][j] = 1for allj.
- When the column (
- Set up the table with the base cases:
- DP Transition:
- Traverse the table row by row and column by column:
- Excluding largest number: Use the value from the column immediately left (
table[i][j-1]). - Including largest number: Check if it's valid (only if
i-j >= 0) and addtable[i-j][j].
- Excluding largest number: Use the value from the column immediately left (
- Traverse the table row by row and column by column:
- Boundary Handling:
- Ensure that
i-j >= 0before accessingtable[i-j][j]. If not, skip adding from the reduced sum.
- Ensure that
Code
C++
class Solution {
public:
int integerPartition(int sum, int largestNumber) {
// Initialize DP table
vector<vector<int>> dp(sum + 1, vector<int>(largestNumber + 1, 0));
// Base cases
for (int j = 0; j <= largestNumber; j++) {
dp[0][j] = 1; // There is one way to partition sum=0
}
for (int i = 1; i <= sum; i++) {
for (int j = 1; j <= largestNumber; j++) {
dp[i][j] = dp[i][j - 1]; // Exclude the current largest number
if (i - j >= 0) {
dp[i][j] += dp[i - j][j]; // Include the current largest number
}
}
}
return dp[sum][largestNumber];
}
};
// Example usage
int main() {
Solution solution;
int sum = 100;
int largestNumber = 99;
cout << "Number of partitions for sum=" << sum << " with largestNumber="
<< largestNumber << ": " << solution.integerPartition(sum, largestNumber) << endl;
return 0;
}
Java
public class Solution {
public int integerPartition(int sum, int largestNumber) {
// Initialize DP table
int[][] dp = new int[sum + 1][largestNumber + 1];
// Base cases
for (int j = 0; j <= largestNumber; j++) {
dp[0][j] = 1; // There is one way to partition sum=0
}
for (int i = 1; i <= sum; i++) {
for (int j = 1; j <= largestNumber; j++) {
dp[i][j] = dp[i][j - 1]; // Exclude the current largest number
if (i - j >= 0) {
dp[i][j] += dp[i - j][j]; // Include the current largest number
}
}
}
return dp[sum][largestNumber];
}
// Example usage
public static void main(String[] args) {
Solution solution = new Solution();
int sum = 100;
int largestNumber = 99;
System.out.println(
"Number of partitions for sum=" + sum + " with largestNumber=" + largestNumber + ": "
+ solution.integerPartition(sum, largestNumber));
}
}
Python
class Solution:
def integerPartition(self, sum, largestNumber):
# Initialize DP table
dp = [[0 for _ in range(largestNumber + 1)] for _ in range(sum + 1)]
# Base cases
for i in range(largestNumber + 1):
dp[0][i] = 1 # There is one way to partition sum=0
for i in range(1, sum + 1):
for j in range(1, largestNumber + 1):
dp[i][j] = dp[i][j - 1] # Exclude the current largest number
if i - j >= 0:
dp[i][j] += dp[i - j][j] # Include the current largest number
return dp[sum][largestNumber]
# Example usage
solution = Solution()
sum = 100
largestNumber = 99
print(f"Number of partitions for sum={sum} with largestNumber={largestNumber}: {solution.integerPartition(sum, largestNumber)}")
Complexity
- ⏰ Time complexity:
O(n^2). The table is filled iteratively acrosssum × largestNumbercells. - 🧺 Space complexity:
O(n^2). The table requires storage proportional to its dimensions.