LeetCode 553:最优除法(贪心算法) —— 题解

👋 欢迎阅读

🎯 欢迎来到「最优除法」题解之旅! 本文将带你从"通过添加括号改变除法优先级"这一算术问题出发,深入理解贪心算法 + 数学推导 的精妙之处,并掌握如何利用连除性质快速构造出使结果最大的表达式。

在开始之前,建议你先:

  • 了解题目背景 :这是 LeetCode 553 题,给定一个正整数数组 nums,相邻整数按顺序进行浮点除法(如 [2,3,4] 表示 2/3/4),你可以在任意位置添加括号改变运算顺序,目标是使最终结果最大 ,并以字符串形式返回对应表达式(不含冗余括号)。本质上,这是一个关于除法优先级的最优化问题。

  • 明确学习目标 :掌握贪心推导的结论 ------对于 nums[0] / nums[1] / ... / nums[n-1],最大结果表达式为 nums[0] / (nums[1] / nums[2] / ... / nums[n-1]),即把除了第一个数以外的所有数用括号括起来并连续相除 。理解为什么这样构造能最大化(因为除法中分母越小结果越大,而将后面的数相除会使分母最小化),并熟练处理边界情况n=1n=2 时无需括号)。


一、题目

553. 最优除法 - 力扣(LeetCode)

二、做题思路

1. 问题分析(前置分析)

给定一个正整数数组 nums,要求通过添加括号改变浮点除法的运算顺序,使得整个表达式的值 最大

基础形式nums[0] / nums[1] / nums[2] / ...,括号可以改变除法的结合性。

核心观察 :由于所有数均为 正整数 ,且数组长度至少为 2,将第一个数作为分子,其余所有数作为分母 ,并将分母部分整体括起来,能使结果最大化。


2. 贪心策略(核心决策规则)

  • 特殊情况 :若 n == 1,直接返回该数。

  • 特殊情况 :若 n == 2,返回 nums[0] / nums[1],无需括号。

  • 一般情况(n >= 3)

    • 构造表达式:nums[0] / (nums[1] / nums[2] / ... / nums[n-1])

    • 这等价于 nums[0] * nums[2] * ... * nums[n-1] / nums[1] ,即 分子为第一个数乘以除第二个数以外的所有数,分母仅为第二个数。

    • 因为所有数 >= 2,这种形式使得分母尽可能小(仅一个数),从而结果最大。


3. 正确性说明(简单版本)

设数组为 a, b, c, d, ...(所有数 ≥ 2)。

原始表达式 a / b / c / ... 默认从左到右,相当于 a / (b * c * ...)

在分母中加入乘法会扩大分母,从而减小整体值 。为了最大化结果,我们应尽可能让分母变小 ,即让尽量少的数留在分母

通过添加括号,我们可以将 a 以外的所有数"压缩"到分子上,具体做法是 a / (b / c / d / ...),而 (b / c / d / ...) 等价于 b / (c * d * ...),但由于括号内的除法会变为乘法,最终结果化为 a * c * d * ... / b,这时分母仅剩 b,是可能的最简形式。

这种形式是唯一的全局最大值 ,因为任何其他加括号方式都会让至少一个数(除 b 外)留在分母,或让 b 移到分子,导致分母变大或分子变小,结果不会更大。


4. 实现细节(边界防护)

  • 处理 n == 1n == 2 的特殊情况。

  • 对于 n >= 3构造字符串 :先添加 nums[0] + "/(",然后循环添加 nums[1]nums[n-2] 并用 '/' 连接,最后添加 nums[n-1] + ")"。。


5. 返回值(目标映射)

返回构造好的最优表达式字符串,即 满足最大值的表达式

四、代码

cpp 复制代码
class Solution 
{
public:
    string optimalDivision(vector<int>& nums) 
    {
        int n = nums.size();
    // 1. 特殊情况:只有一个数时,直接返回该数(无除法)
    if (n == 1) 
    {
        return to_string(nums[0]);
    }

    // 2. 特殊情况:只有两个数时,直接相除,无需括号
    if (n == 2) 
    {
        return to_string(nums[0]) + '/' + to_string(nums[1]);
    }

    // 3. 一般情况:n &gt;= 3
    //    数学结论:为了使除法表达式结果最大,应该将 nums[0] 作为分子,
    //    其余所有数作为分母,并且将分母部分用括号括起来,
    //    即 nums[0] / (nums[1] / nums[2] / ... / nums[n-1])
    //    这样等价于 nums[0] * nums[2] * ... * nums[n-1] / nums[1],
    //    是能得到的最大值(因为所有数都为正整数)。
    string ret;

    // 构造:nums[0] + '/' + '('
    ret += to_string(nums[0]) + '/' + '(';

    // 中间部分:nums[1] / nums[2] / ... / nums[n-2](最后一项单独处理)
    for (int i = 1; i &lt; n - 1; i++) 
    {
        ret += to_string(nums[i]) + '/';
    }

    // 最后一项:nums[n-1] + ')'
    ret += to_string(nums[n - 1]) + ')';

    // 4. 返回构造好的最优表达式字符串
    return ret;
}
};

五、流程图

六、正确性说明(详细版)

步骤 1:符号与问题建模
复制代码
+----------------------------------------------------+
|  输入:正整数数组 nums = [a₁, a₂, ..., aₙ]        |
|  操作:在相邻数之间插入除号,可加任意括号改变优先级|
|  目标:使整个表达式的值最大,返回对应字符串        |
+----------------------------------------------------+
                    |
                    v
+----------------------------------------------------+
|  贪心策略:当 n ≥ 3 时,构造表达式                 |
|  a₁ / (a₂ / a₃ / ... / aₙ)                       |
|  即:a₁ 作为分子,其余所有数放在一个括号内连除。   |
|  该表达式等价于 a₁ * a₃ * a₄ * ... * aₙ / a₂。   |
|  贪心实质:通过括号将 a₂ 单独放在分母最底层,      |
|  而 a₃..aₙ 变成乘数,最大化分子/分母比值。         |
+----------------------------------------------------+
  • 设数组元素为 a₁, a₂, ..., aₙ(图二中用 a,b,c,d,e,f 表示)。

  • 由于所有数均为正整数(且原题无限制,但图二提到 ≥2,这使结论更强),除法运算等价于乘以倒数,因此加括号的本质是决定哪些数乘到分子、哪些乘到分母。

  • 贪心结论 :最优表达式为 a₁ / (a₂ / a₃ / ... / aₙ),即把第一个数作为分子,剩余所有数作为分母并整体括起来。


步骤 2:关键性质 ------ 最优表达式结构(反证法与不等式)
复制代码
+----------------------------------------------------+
|  性质 1:任意加括号的表达式都可以化为             |
|  a₁ * X / Y 的形式,其中 X 是 a₃..aₙ 中某些数的   |
|  乘积,Y 是 a₂..aₙ 中某些数的乘积,且 a₂ 必然在 Y |
|  中(因为 a₁ 是第一个数,除号紧跟在 a₁ 后)。      |
|  反证:若 a₂ 不在分母,则 a₂ 变为乘数,会增大     |
|  分子,但 a₂ 右侧的数会相应调整,总效果是          |
|  分母变小,但无法超过将 a₂ 单独放分母的极端情况。  |
+----------------------------------------------------+
                     |
                     v
+----------------------------------------------------+
|  性质 2(核心不等式):对于任意 i ≥ 3,           |
|  将 a_i 放在分母中会除以 a_i,而放在分子中会乘以  |
|  a_i。由于 a_i ≥ 2(图二条件),乘比除大,        |
|  因此为了最大化,应让尽可能多的数(除 a₂ 外)     |
|  进入分子。但 a₂ 必须留在分母(因为紧跟 a₁ 的除号 |
|  必然将 a₂ 置于分母),所以最佳是让 a₃..aₙ 全在   |
|  分子,即表达式为 a₁ / (a₂ / a₃ / ... / aₙ)。     |
|  不等式示意:若将某个 a_i (i≥3) 放到分母,则值    |
|  变为原来的 1/a_i² 倍(因为原来乘 a_i,现在除     |
|  a_i),显然减小。                                 |
+----------------------------------------------------+

详细论证

  • 性质1(结构分解) :任何加括号的除法表达式,由于第一个数是 a₁,且第一个运算符是除号,所以 a₁ 必然作为分子。其后的所有数通过括号和除法组织,最终可以看作 a₁ 乘以某些数的乘积,再除以某些数的乘积。a₂ 必然出现在分母中 ,因为 a₁ / a₂ 是最先形成的除法关系,无论括号如何,a₂ 不能变为乘数(除非有括号将其与后续数组合,但 a₁ 除以的是一个整体,该整体包含 a₂a₂ 在分母位置)。

  • 性质2(最优化选择) :对于任意 i ≥ 3a_i 放在分子比放在分母更优 。若当前 a_i 在分母,将其移到分子(通过调整括号),表达式值会乘以 a_i^2(因为原来除以 a_i,现在乘以 a_i)。由于 a_i ≥ 2a_i^2 > 1,值严格增大。因此,最优解必须让所有 a₃..aₙ 都在分子。唯一必须留在分母的是 a₂ ,故最优形式为 a₁ * a₃ * a₄ * ... * aₙ / a₂,即 a₁ / (a₂ / a₃ / ... / aₙ)


步骤 3:归纳证明 ------ 贪心构造即为全局最优
复制代码
+----------------------------------------------------+
|  初始:n 个数,已确定 a₁ 为分子。                  |
|  归纳假设:对于后缀 a₂..aₙ,最优分母构造是         |
|  让 a₂ 单独留在分母,其余数通过嵌套括号转化为乘数。 |
+----------------------------------------------------+
                     |
                     v
+----------------------------------------------------+
|  处理 a₂:它必须留在分母(因为紧跟 a₁ 的除号)。   |
|  处理 a₃..aₙ:由性质2,每个都应进入分子。          |
|  构造括号:a₂ / a₃ / ... / aₙ 整体作为分母,       |
|  因为连除等价于 a₂ / (a₃ * ... * aₙ),即 a₂ 在    |
|  分母,a₃..aₙ 实际上变为乘数(因为除以一个除式     |
|  等于乘以该除式倒数的乘积)。                       |
|  最终表达式 a₁ / (a₂ / a₃ / ... / aₙ) 达到最大。  |
+----------------------------------------------------+
                     |
                     v
+----------------------------------------------------+
|  结论:该贪心策略得到最大表达式值,且字符串为      |
|  a₁/(a₂/a₃/.../aₙ)(去掉冗余括号后返回)。         |
+----------------------------------------------------+
  • 终止 :当处理完所有数,得到表达式即为最大值。若 n=12,直接返回无需括号。

🎯 闭幕

🎉 恭喜你完成了「最优除法」问题的学习!

为了巩固知识并进一步拓展,建议你:

🚀 动手实践

在 LeetCode 上提交代码,尝试不同的测试用例。

💡 深入思考

  • 本题采用 贪心策略 :对于长度大于 2 的数组,最优表达式为 nums[0] / (nums[1] / nums[2] / ... / nums[n-1])为什么这样能让结果最大? 你能从除法的数学性质(例如除以一个分数等于乘以它的倒数)来解释吗?

  • 代码中分别处理了 n == 1n == 2 的情况。如果长度为 1 或 2 时也套用通用括号形式,会得到什么结果? 为什么需要单独处理?

  • n >= 3 时,括号从第二个数开始括到最后一个数。为什么不在第一个数后面直接加括号? 例如 (nums[0]/nums[1])/nums[2]nums[0]/(nums[1]/nums[2]) 哪种更大?用例子验证一下。

📚 延伸挑战

  • 将题目改为 求表达式的最小值,你应该如何调整括号位置?只需要改变哪里的策略?

  • 如果要求 返回最大值对应的数值(而不是表达式),代码需要做哪些修改?

如果你觉得本文对你有所帮助,欢迎:

👍 点赞 / 收藏

👤 关注作者,获取更多题解

💬 留言交流你的疑问或优化思路

祝你在 算法之路 上越走越稳,早日攻克每一道难题!下次见 🚀✨

相关推荐
雾喔1 小时前
算法练习7
java·数据结构·算法
可编程芯片开发2 小时前
基于模糊PID控制的六步逆变器供电无刷直流电机调速simulink仿真
算法
imperialeast3 小时前
WinAXP音乐播放器8月4日更新
windows·算法·ui
ZhouDevin3 小时前
算法论文/模型微调1——CNN架构做lora微调训练
人工智能·深度学习·算法·架构·cnn
小月土星3 小时前
LeetCode 21. 合并两个有序链表 超详细题解(虚拟头结点 + 逐行代码拆解)
算法
oier_Asad.Chen3 小时前
【OI学习笔记】Floyed-Warshall算法解决传递闭包问题
c++·笔记·学习·算法·图论·最短路·传递闭包
国科安芯3 小时前
卫星星务计算机数据管理中SEU容错机制的技术实现研究——以AS32S601存储器ECC架构为例
网络·单片机·算法·fpga开发·架构·云计算·risc-v
孙启超3 小时前
【AI应用开发】Agent 无限 loop(反复调用同一个工具)如何规避?
人工智能·python·算法·llm·agent·loop·ai应用开发
zmzb01033 小时前
C++课后习题训练记录Day179
开发语言·c++