![]() |
VOOZH | about |
Given two numbers n and k where n represents a number of elements in a set, find a number of ways to partition the set into k subsets.
Example:
Input: n = 3, k = 2
Output: 3
Explanation: Let the set be [1, 2, 3], we can partition it into 3 subsets in the following ways [[1,2], [3]], [[1], [2,3]], [[1,3], [2]].Input: n = 3, k = 1
Output: 1
Explanation: There is only one way [[1, 2, 3]].
Table of Content
We can recursively calculate the number of ways to partition a set of n elements by considering each element and either placing it in an existing subset or creating a new subset. For each element, we calculate the number of ways to partition the remaining n-1 elements into k subsets and then sum these values for all possible k. This gives us the following recurrence relation for Stirling numbers of the second kind:
- The previous n – 1 elements are divided into k partitions, i.e S(n-1, k) ways. Put this nth element into one of the previous k partitions. So, count = k * S(n-1, k)
- The previous n – 1 elements are divided into k – 1 partitions, i.e S(n-1, k-1) ways. Put the nth element into a new partition ( single element partition). So, count = S(n-1, k-1)
Base case: Return 0 for invalid cases (n == 0, k == 0, or k > n) and 1 for trivial cases (k == 1 or k == n).
Total count = S(n, k) = k * S( n - 1, k) + S(n - 1, k - 1)
15
If we notice carefully, we can observe that the above recursive solution holds the following two properties of Dynamic Programming:
1. Optimal Substructure: The number of ways to partition a set of n elements into k subsets depends on two smaller subproblems:
- The number of ways to partition the first n-1 elements into k subsets, and
- The number of ways to partition the first n-1 elements into k-1 subsets and then add the new element as its own subset. By combining these optimal solutions, we can efficiently calculate the total number of ways to partition the set.
2. Overlapping Subproblems: In the recursive approach, certain subproblems are recalculated multiple times. For example, when computing S(n, k), the subproblems S(n-1, k) and S(n-1, k-1) are recomputed multiple times. This redundancy leads to overlapping subproblems, which can be avoided using memoization or tabulation. Below is recursion tree of countP(10, 7). The subproblem countP(8,6) or CP(8,6) is called multiple times.
15
The approach is similar to the previous one, but instead of solving the problem recursively, we can calculate the solution using a bottom-up dynamic programming approach. We maintain a 2D table dp[][]where dp[i][j] represents the number of ways to partition a set of i elements into j subsets.
Base Case:
- For i = 0 and 0 <= j < n, dp[0][j] = 0 (no elements to partition into subsets).
- For j = 0 and 0 <= i < n, dp[i][0] = 0(cannot partition i elements into 0 subsets).
- For i = j, dp[i][j] = 1(one way to partition i elements into i subsets).
Recursive Case:
- For i > 1 and j > 1,
- dp[i][j] = j * dp[i-1][j] + dp[i-1][j - 1]
- This formula arises from considering two options: either placing the i-th element in one of the existing subsets (which is j * dp[i-1][j]) or placing it in a new subset (dp[i-1][j-1]).
15
In previous approach the current value dp[i][j] is only depend upon the current and previous row values of DP. So to optimize the space complexity we use a single 1D array to store the computations.
15
Related article: