要判断一个数组是否能被分成 M 堆,每堆的元素相加值相等,可以采用 回溯法 或 动态规划 (DP)。以下是解决思路和代码示例:
问题分析
1. 如果数组 \text{nums} 的总和 \text{sum(nums)} 不能被 M 整除,直接返回 False。
2. 如果可以被整除,那么每堆的目标和为 \text{targetSum} = \text{sum(nums)} / M 。
3. 使用回溯或动态规划尝试分堆。
方法 1: 回溯法 (Backtracking)
通过回溯法尝试将数组分配到 M 个堆中,确保每堆的和等于 \text{targetSum} 。
Python 示例代码:
def can_partition_k_subsets(nums, M):
total = sum(nums)
if total % M != 0:
return False
targetSum = total // M
nums.sort(reverse=True) # 优化:先对数组排序,优先尝试大数
buckets = [0] * M # 每堆当前的累加和
def backtrack(index):
# 如果所有数字都分配完,检查是否每堆都满足 targetSum
if index == len(nums):
return all(bucket == targetSum for bucket in buckets)
# 尝试将当前数字分配到每个堆
for i in range(M):
if buckets[i] + nums[index] > targetSum: # 剪枝
continue
buckets[i] += nums[index]
if backtrack(index + 1):
return True
buckets[i] -= nums[index] # 回溯
# 如果当前堆为空,后续分配也不可能成功,直接退出
if buckets[i] == 0:
break
return False
return backtrack(0)
方法 2: 动态规划 (Bitmask DP)
通过位掩码表示集合状态,逐步填充每堆,最终判断是否可以将所有数字分配到 M 个堆中。
Python 示例代码:
def can_partition_k_subsets(nums, M):
total = sum(nums)
if total % M != 0:
return False
targetSum = total // M
n = len(nums)
nums.sort(reverse=True) # 优化:先排序,大数优先分配
dp = [-1] * (1 << n) # dp[state] 表示当前 state 下的累加和
dp[0] = 0
for state in range(1 << n):
if dp[state] == -1:
continue
for i in range(n):
if not (state & (1 << i)) and dp[state] + nums[i] <= targetSum:
next_state = state | (1 << i)
dp[next_state] = (dp[state] + nums[i]) % targetSum
return dp[-1] == 0
用法示例
nums = [4, 3, 2, 3, 5, 2, 1]
M = 4
result = can_partition_k_subsets(nums, M)
print(result) # 输出 True 或 False
复杂度
zhizhi 1. 回溯法:
• 时间复杂度: O(M \cdot 2^n) ,最坏情况需要遍历所有分配方案。
• 空间复杂度: O(M + n) (递归栈 + 辅助数组)。
2. 动态规划:
• 时间复杂度: O(n \cdot 2^n) ,需要计算每个状态。
• 空间复杂度: O(2^n) (状态数组)。
方法选择
• 如果数组较小 ( n \leq 20 ),优先使用动态规划,效率较高。
• 如果数组较大且 M 较小,回溯法可能更适合。