如何优化三分组最小化最大和的O(3^n)复杂度算法
三分组最小最大和算法优化方案
问题回顾
需将正整数数组划分为3个分组,最小化三组的最大和值。原算法采用暴力回溯,时间复杂度O(3^n),远慢于作业要求的O(l²n³),需针对性优化。
核心优化思路
1. 预排序剪枝
先将数组降序排序,优先处理大元素。大元素对分组容量的限制更严格,能快速排除不可能的分支,大幅减少无效递归/计算。
2. 记忆化搜索+状态去重
原递归的状态(n, a, b, c)存在大量重复(分组无序,(a,b,c)与(b,a,c)等价),通过以下方式优化:
- 对剩余容量排序,将状态简化为
(idx, rem1, rem2, rem3)(rem1≥rem2≥rem3),减少重复状态数。 - 用记忆化缓存已计算的状态,避免重复计算。
3. 动态规划(DP)实现
采用DP替代递归回溯,严格满足时间复杂度要求。定义状态dp[s1][s2]表示是否能选出两个子集和为s1和s2(均≤目标值target),且剩余元素和total-s1-s2 ≤ target。
优化后代码实现
方案一:记忆化回溯(适合中等规模数组)
import functools def works(array, target): # 降序排序,优先处理大元素 sorted_arr = sorted(array, reverse=True) n = len(sorted_arr) @functools.lru_cache(maxsize=None) def backtrack(idx, rem1, rem2, rem3): if idx == n: return True num = sorted_arr[idx] # 剪枝:当前元素大于所有剩余容量,直接返回False if num > rem1 and num > rem2 and num > rem3: return False # 尝试放入第一个组,排序剩余容量去重 if rem1 >= num: new_rem = sorted([rem1 - num, rem2, rem3], reverse=True) if backtrack(idx + 1, *new_rem): return True # 仅当剩余容量不同时尝试放入第二个组,避免重复状态 if rem2 >= num and rem2 != rem1: new_rem = sorted([rem1, rem2 - num, rem3], reverse=True) if backtrack(idx + 1, *new_rem): return True # 仅当剩余容量不同时尝试放入第三个组 if rem3 >= num and rem3 != rem2 and rem3 != rem1: new_rem = sorted([rem1, rem2, rem3 - num], reverse=True) if backtrack(idx + 1, *new_rem): return True return False return backtrack(0, target, target, target) def minimum_allowable_attendance_for_long_weekend(input_file_path, output_file_path): import math # 读取输入文件(假设元素以空格/换行分隔) with open(input_file_path, 'r') as f: array = list(map(int, f.read().split())) total = sum(array) max_num = max(array) # 初始目标值:取数组最大值和总和1/3的上界中的较大者 start_target = max(max_num, math.ceil(total / 3)) answer = total # 最坏情况:所有元素在一个组 for target in range(start_target, total + 1): if works(array, target): answer = target break # 写入结果到输出文件 with open(output_file_path, 'w') as f: f.write(str(answer))
方案二:动态规划(满足O(l²n³)时间复杂度要求)
def works_dp(array, target): total = sum(array) # 初始化DP表:prev_dp[s1][s2]表示能否得到两个子集和为s1、s2 prev_dp = [[False] * (target + 1) for _ in range(target + 1)] prev_dp[0][0] = True for num in array: # 复制上一轮状态,对应"不放入任何子集"的选择 curr_dp = [row.copy() for row in prev_dp] for s1 in range(target + 1): for s2 in range(target + 1): if prev_dp[s1][s2]: # 放入第一个子集 if s1 + num <= target: curr_dp[s1 + num][s2] = True # 放入第二个子集 if s2 + num <= target: curr_dp[s1][s2 + num] = True prev_dp = curr_dp # 检查是否存在符合条件的子集组合 for s1 in range(target + 1): for s2 in range(target + 1): if prev_dp[s1][s2] and (total - s1 - s2) <= target: return True return False def minimum_allowable_attendance_for_long_weekend(input_file_path, output_file_path): import math with open(input_file_path, 'r') as f: array = list(map(int, f.read().split())) total = sum(array) max_num = max(array) start_target = max(max_num, math.ceil(total / 3)) answer = total for target in range(start_target, total + 1): if works_dp(array, target): answer = target break with open(output_file_path, 'w') as f: f.write(str(answer))
复杂度分析
- 记忆化回溯:时间复杂度约为O(n*target²),状态数被排序去重后大幅减少,实际运行效率远高于原O(3^n)算法。
- 动态规划:时间复杂度为O(ntarget²),其中
target的数量级为O(nmax_element),代入后得到O(n³*max_element²),完全符合作业要求的O(l²n³)(l为数组元素最大值)。
内容的提问来源于stack exchange,提问作者Biwerg
相关产品推荐
相关产品推荐

