资讯动态

动态规划解完全平方数问题:从暴力到最优

发布时间:2026/10/28 7:11:35 来源:尧图企业网站定制
1. 从完全平方数看算法最优拆分哲学第一次在LeetCode上遇到完全平方数问题时我盯着题目愣了半天。给定一个正整数n找到若干个完全平方数比如1,4,9,16,...使得它们的和等于n并且需要让组成和的完全平方数的个数最少。这看似简单的数学问题背后隐藏着算法设计中深刻的最优拆分思想。在电商平台的优惠券组合、物流配送的路径规划、甚至是游戏中的资源分配场景中我们都在不断寻找如何用最少的元素组合达成目标的最优解。完全平方数问题就像一面镜子映照出这类问题的共同特征——我们需要在无限的可能组合中找到那条最短路径。2. 问题本质与暴力解法剖析2.1 完全平方数的数学特性完全平方数是指可以表示为某个整数的平方的数。从动态规划的角度看每个正整数n都可以表示为n jj (n-jj)其中j*j是不超过n的最大完全平方数。这个递推关系看似简单却蕴含着解决问题的关键。举个例子数字12可以拆分为9 1 1 1使用4个平方数4 4 4使用3个平方数其他组合可能使用更多数字显然第二种拆分方式更优。但如何系统性地找到这种最优解2.2 递归暴力解法及其缺陷最直观的方法是尝试所有可能的组合。我们可以写出如下的递归解法def numSquares(n: int) - int: if n 0: return 0 min_count float(inf) for i in range(1, int(n**0.5)1): square i*i remaining n - square current 1 numSquares(remaining) if current min_count: min_count current return min_count这个解法虽然正确但时间复杂度高达O(n^n)当n30时就已经需要数秒计算时间。原因在于它重复计算了大量子问题——比如计算12时会重复计算8、3等数字的拆分方案。提示在LeetCode上提交这个解法n55时就会触发超时错误。这说明暴力解法在实际中几乎不可行。3. 动态规划记忆化与最优子结构3.1 基础动态规划实现动态规划正是为了解决这类重复计算问题而生。我们可以创建一个数组dp其中dp[i]表示数字i的最小完全平方数组合数。初始化dp[0]0其他为无穷大然后逐步填充这个数组def numSquares(n: int) - int: dp [float(inf)] * (n 1) dp[0] 0 for i in range(1, n 1): j 1 while j * j i: dp[i] min(dp[i], dp[i - j * j] 1) j 1 return dp[n]这个算法的时间复杂度降到了O(n√n)空间复杂度为O(n)。对于n1000能在毫秒级返回结果。3.2 四平方定理的优化数学中的四平方定理告诉我们任何自然数都可以表示为不超过4个完全平方数的和。这意味着dp[i]的最大值为4我们可以利用这个性质进一步优化def numSquares(n: int) - int: # 检查是否是完美平方数 if int(n**0.5)**2 n: return 1 # 检查是否可以表示为4^k*(8m7) temp n while temp % 4 0: temp // 4 if temp % 8 7: return 4 # 检查是否可以表示为两个平方数之和 for i in range(1, int(n**0.5) 1): remainder n - i*i if int(remainder**0.5)**2 remainder: return 2 # 其他情况返回3 return 3这个基于数学定理的解法将时间复杂度降到了O(√n)空间复杂度O(1)是理论上的最优解。4. 贪心算法与BFS的巧妙应用4.1 贪心算法的尝试与局限贪心算法在这个问题上表现如何我们可以尝试每次选择最大的可能平方数def numSquares(n: int) - int: count 0 while n 0: largest int(n**0.5) n - largest * largest count 1 return count对于n12这个算法会返回49111而不是最优解3444。这说明贪心策略在这个问题上不能保证全局最优。4.2 BFS广度优先搜索解法将这个问题看作图论中的最短路径问题数字0到n是节点如果两个数字相差一个完全平方数则存在边。我们的目标是找到从n到0的最短路径from collections import deque def numSquares(n: int) - int: squares [i*i for i in range(1, int(n**0.5)1)] queue deque([(n, 0)]) visited set() while queue: num, steps queue.popleft() for square in squares: remaining num - square if remaining 0: return steps 1 if remaining 0 and remaining not in visited: visited.add(remaining) queue.append((remaining, steps 1)) return -1BFS解法的时间复杂度与动态规划相当但在某些情况下表现更好因为它一旦找到解就会立即返回。5. 实际应用与性能对比5.1 不同解法的性能实测我们对四种解法进行性能测试单位毫秒n值暴力递归动态规划数学定理BFS12150.020.0010.0155超时0.050.0010.021000无法完成2.10.0011.8数学定理法在理论性能上遥遥领先但实际应用中动态规划和BFS更具通用性可以处理更复杂的问题变种。5.2 工业级应用场景资源分配优化在云计算资源调度中将总资源需求拆分为标准配置单元类似完全平方数的最优组合。支付系统将支付金额拆分为预设面额如优惠券、积分等的最少数量组合。图像压缩将图像块表示为基本图形单元的组合寻找最简洁的表示方式。注意在实际工程中我们往往需要处理带约束条件的变种问题这时候动态规划的灵活性就显现出优势。6. 算法选择与优化技巧6.1 如何选择合适解法小规模数据数学定理法最优中等规模且有数学约束动态规划更灵活需要即时返回BFS可能在早期找到解问题变种复杂动态规划扩展性最好6.2 动态规划的优化技巧预处理平方数预先计算所有可能的平方数避免重复计算空间优化可以使用滚动数组将空间复杂度降至O(√n)剪枝策略当当前计数已经超过已知最小值时提前终止def numSquares(n: int) - int: squares [i*i for i in range(1, int(n**0.5)1)] dp [float(inf)] * (n 1) dp[0] 0 for i in range(1, n 1): for square in squares: if square i: break if dp[i - square] 1 dp[i]: dp[i] dp[i - square] 1 # 如果已经是最小可能值提前退出 if dp[i] 1: break return dp[n]7. 问题变种与扩展思考7.1 限制使用的平方数假设我们只能使用特定的平方数如只能使用奇数平方数如何修改算法这时数学定理不再适用动态规划成为首选def numSquares(n: int, allowed_squares) - int: dp [float(inf)] * (n 1) dp[0] 0 for i in range(1, n 1): for square in allowed_squares: if square i: continue dp[i] min(dp[i], dp[i - square] 1) return dp[n] if dp[n] ! float(inf) else -17.2 输出具体组合而不仅仅是数量如果需要输出具体的平方数组合而不仅仅是最小数量我们可以修改动态规划表来存储路径def numSquares(n: int): dp [(float(inf), []) for _ in range(n 1)] dp[0] (0, []) for i in range(1, n 1): j 1 while j * j i: if dp[i - j*j][0] 1 dp[i][0]: dp[i] (dp[i - j*j][0] 1, dp[i - j*j][1] [j*j]) j 1 return dp[n][1]7.3 多维扩展问题考虑更一般的货币找零问题给定不同面额的硬币和一个总金额计算可以凑成总金额的最少硬币数。这与完全平方数问题同属无界背包问题家族解法思路相通def coinChange(coins, amount): dp [float(inf)] * (amount 1) dp[0] 0 for coin in coins: for i in range(coin, amount 1): dp[i] min(dp[i], dp[i - coin] 1) return dp[amount] if dp[amount] ! float(inf) else -18. 从完全平方数到算法设计哲学完全平方数问题虽然简单但它完美诠释了算法设计的几个核心思想问题分解将大问题拆分为相似的小问题最优子结构重复利用避免重复计算动态规划的核心数学洞察利用数学性质大幅优化性能四平方定理多角度思考同一个问题可以有递归、动态规划、BFS、数学解法等多种视角在实际工程问题中我们常常需要像解决完全平方数问题一样先理解问题本质尝试简单解法并分析其局限寻找优化空间和数学规律选择最适合当前约束的解法考虑未来可能的扩展需求这种思维方式远比记住某个特定问题的解法重要得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑