LeetCode 剑指 Offer 14- I. 剪绳子
题目描述

题意分析
输入是一根长度为 $n$ 的整数长度绳子,要求剪成至少两段,每段长度都是正整数,输出这些段长度乘积的最大值。
「至少两段」是最强的约束信号:它意味着不允许「一刀不剪」,所以答案并不是 $n$ 本身。当 $n$ 很小、小到剪一刀反而亏的时候,我们仍然必须剪,这就制造了两个必须单独处理的边界。
数据范围 $2 \le n \le 58$ 也是一个信号。它小到可以用一个长度为 $n + 1$ 的数组做递推,同时最大答案($n = 58$ 时为 $3^{18} \times 4 = 1549681956$)仍然落在 32 位有符号整数范围内,因此不需要取模,也不需要大数。这一点和「剪绳子 II」正好相反。
需要单独确认的边界有三个:$n = 2$ 只能剪成
1 + 1,答案是 1;$n = 3$ 只能剪成1 + 2或1 + 1 + 1,答案是 2;而从 $n = 4$ 开始,剪一刀就不再吃亏(2 + 2得到 4,正好等于 $n$),之后剪得越合理乘积增长越快。另外要注意「相同长度的段可以重复出现」,题目没有要求各段互不相同,所以最优解通常是一堆相同的小段。
解法:贪心优先剪出长度 3
核心思路
对任意长度
x >= 5,把它拆成3和x - 3后,乘积满足3(x - 3) > x,因此最优答案中不会保留长度大于等于 5 的整段。最终只需考虑 2、3、4。在相同总长度下,3 比 2 更适合作为主要因子:
3 × 3 = 9 > 2 × 2 × 2 = 8。所以应尽量剪出 3,但不能留下 1;若余数为 1,应把最后的3 + 1改成2 + 2,因为 4 大于 3。循环条件写成
n > 4,每次剪出一个 3。退出时剩余长度只能是 2、3 或 4:2、3 可作为最后一段,4 对应2 × 2,数值仍是 4。这样余 1 的情况会自然调整为 4。
解题步骤
n <= 3时必须剪一刀,返回n - 1。- 初始化乘积
result = 1。- 当剩余长度大于 4 时,剪出一段 3:
result *= 3、n -= 3。- 将最后的 2、3 直接乘入结果;剩余 4 时按
2 × 2理解,乘积同样是 4。例如
n = 10:连续剪出两个 3,剩余 4,得到3 × 3 × 4 = 36,等价于3 + 3 + 2 + 2。
代码实现
class Solution {
public int cuttingRope(int n) {
if (n <= 3) {
return n - 1;
}
int result = 1;
while (n > 4) {
result *= 3;
n -= 3;
}
return result * n;
}
}
func cuttingRope(n int) int {
if n <= 3 {
return n - 1
}
result := 1
for n > 4 {
result *= 3
n -= 3
}
return result * n
}
复杂度分析
- 时间复杂度:$O(n)$。循环每次将剩余长度减少 3;在本题
n <= 58时轮数很少。- 空间复杂度:$O(1)$。只使用常数个变量。
关键点总结
- “至少剪成两段”使
n = 2、3必须返回n - 1,不能直接返回n。- 长度大于等于 5 的段继续拆出 3 会严格增大乘积。
- 尽量使用 3,但绝不能留下 1;
3 + 1应调整为2 + 2。- 保留最后的 4,可用一个
n > 4的循环统一处理余数,无需分类代码。
易错点总结
n <= 3时返回n,等价于没有剪绳子,违反题意。- 循环写成
n >= 4会把剩余 4 拆成3 + 1,乘积反而变小。- 始终剪 2 并非最优,例如长度 6 时
3 × 3 > 2 × 2 × 2。- 本题不要求取模;不要直接套用“剪绳子 II”的代码。
相似题目
| 题目 | 难度 | 考察点 |
|---|---|---|
| 343. 整数拆分 | 中等 | 整数拆分求最大乘积 |
| 279. 完全平方数 | 中等 | 拆分为完全平方数之和 |
| 剑指 Offer 14- II. 剪绳子 II | 中等 | 大数取模与快速幂 |