Given an integer array nums
and an integer k
, return true
if it is possible to divide this array into k
non-empty subsets whose sums are all equal.
Example 1:
Input: nums = [4,3,2,3,5,2,1], k = 4 Output: true Explanation: It is possible to divide it into 4 subsets (5), (1, 4), (2,3), (2,3) with equal sums.
Example 2:
Input: nums = [1,2,3,4], k = 3 Output: false
Constraints:
1 <= k <= nums.length <= 16
1 <= nums[i] <= 104
[1, 4]
.This problem asks whether a given integer array nums
can be partitioned into k
non-empty subsets, each with the same sum. The solution involves several approaches, each with its own trade-offs in terms of time and space complexity.
Core Idea: The fundamental approach across all solutions involves checking if the sum of elements in nums
is divisible by k
. If not, a partition into equal-sum subsets is impossible. If it is divisible, the problem reduces to finding k
subsets with a sum of targetSum = sum(nums) / k
.
This solution uses Depth-First Search (DFS) with several optimizations for efficiency.
Approach:
Preprocessing: Calculate targetSum
. If sum(nums)
is not divisible by k
, return false
. Sort nums
in descending order to improve pruning efficiency (larger numbers are considered first).
DFS function: The recursive dfs(index)
function explores possible subset assignments. index
tracks the current element being considered in nums
.
Iteration and Pruning: For each element, the algorithm attempts to add it to each of the k
subsets (represented by an array cur
of size k
). Pruning is applied:
targetSum
, it's skipped for that subset.cur[j] == cur[j-1]
), it indicates that subset is complete, and the algorithm proceeds to the next one.Base Case: If index
reaches the end of nums
, it means all elements have been successfully assigned, so true
is returned. Otherwise, false
is returned.
Time Complexity: O(kn), where n is the length of nums
. The worst-case scenario is trying all possible combinations of assigning elements to subsets. The sorting step is O(n log n). However, the pruning significantly reduces the search space in practice.
Space Complexity: O(n) due to the recursive call stack and the cur
array.
This solution uses bit manipulation to represent the state of element assignment and memoization to avoid redundant calculations.
Approach:
State Representation: A bitmask state
(an integer) represents which elements have been included in subsets. The i-th bit being 1 means the i-th element is already assigned.
Memoization: A cache
(or f
array) stores the results of subproblems (dfs(state, t)
), where t
is the current sum of the subset being built. cache[state]
indicates whether it's possible to partition the remaining elements (represented by state
) given the current subset sum.
DFS Function: The dfs(state, t)
function recursively explores states:
state
represents all elements assigned (state == mask
), it returns true
.cache
to check if the result for the current state is already known.t + nums[i]
) exceeds targetSum
. If it does, it prunes the search for that element. Otherwise, it recursively calls dfs
with the updated state and subset sum.Time Complexity: O(n * 2n). The state space is 2n (all possible subsets), and for each state, we iterate through n elements.
Space Complexity: O(2n) for the cache
(memoization table).
This approach uses dynamic programming to efficiently determine the feasibility of partitioning.
Approach:
State Definition: f[i]
is a boolean array where f[i] = true
means that there exists a way to partition the elements represented by the bitmask i
into subsets with the desired sum. cur[i]
stores the running sum for that state.
Iteration: It iterates through all possible bitmasks i
from 0 to 2n. For each bitmask, if f[i]
is true, it means a valid partition for that state exists.
Subset Update: For each unassigned element nums[j]
, it checks if adding it to the current subset exceeds targetSum
. If not, and the element is not assigned ((i >> j) & 1 == 0
), it sets f[i | (1 << j)] = true
and updates cur[i | (1 << j)]
.
Result: Finally, it checks f[(1 << n) - 1]
, which represents the state where all elements are assigned.
Time Complexity: O(n * 2n) due to iterating through all possible bitmasks and checking each element.
Space Complexity: O(2n) for the f
and cur
arrays.
Summary Table:
| Solution | Time Complexity | Space Complexity | |-------------------|----------------------|----------------------| | DFS with Pruning | O(kn) (worst case) | O(n) | | State Compression | O(n * 2n) | O(2n) | | Dynamic Programming | O(n * 2n) | O(2n) |
The constraint nums.length <= 16
makes the exponential time complexity of solutions 2 and 3 manageable. Solution 1, while potentially having a worse worst-case complexity, can be faster in practice due to pruning. The choice of the best solution depends on the specific input data and performance requirements.