news 2026/9/16 12:33:01

LeetCode-Book 详解剑指 Offer 14-II 剪绳子 II:基于均值不等式的切分策略与大数求余(快速幂取模)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LeetCode-Book 详解剑指 Offer 14-II 剪绳子 II:基于均值不等式的切分策略与大数求余(快速幂取模)

LeetCode-Book 详解剑指 Offer 14-II 剪绳子 II:基于均值不等式的切分策略与大数求余(快速幂取模)

【免费下载链接】LeetCode-Book《剑指 Offer》《图解算法数据结构》《Krahets 笔面试精选 88 题》Python, Java, C++ 解题代码项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Book

本文基于 LeetCode-Book 仓库中《剑指 Offer》部分的 剑指 Offer 14- II. 剪绳子 II 解析文档,系统讲解"剪绳子 II"这道经典的数学推导型动态规划变形题:如何通过算术几何均值不等式证明"尽可能等分为 3"是最优切分策略,以及当n很大时如何用循环求余快速幂求余int范围内安全计算3^a % 1000000007。读完后你将掌握完整的数学推导链条、三种余数情形的切分规则,以及 Python / Java / C++ 三种语言的参考实现细节。

问题背景:与"剪绳子 I"的关系

本题与 剑指 Offer 14- I. 剪绳子 主体等价,唯一不同在于本题目涉及大数越界情况下的求余问题:原书建议先做上一道题(其参考实现见 sfo_14i_cut_the_rope_i_s1.py),在此基础上再研究本题的大数求余方法。

题目的数学本质是:设将长度为 $n$ 的绳子切为 $a$ 段:

$$ n = n_1 + n_2 + ... + n_a $$

本题等价于求解各段长度的最大乘积:

$$ \max(n_1 \times n_2 \times ... \times n_a) $$

并且由于本题结果可能极大,最终需要对质数模数 $p = 1000000007$ 取余返回。

数学推导:为什么最优切段长度是 3

原书的推导总体分为两步:① 当所有绳段长度相等时,乘积最大;② 最优的绳段长度为 $3$。

推论一:等分多段时乘积最大

依据"算术几何均值不等式",等号当且仅当 $n_1 = n_2 = ... = n_a$ 时成立:

$$ \frac{n_1 + n_2 + ... + n_a}{a} \geq \sqrt[a]{n_1 n_2 ... n_a} $$

推论一:将绳子以相等的长度等分为多段,得到的乘积最大。

推论二:最优段长为 3(而非 2)

设将绳子按照 $x$ 长度等分为 $a$ 段,即 $n = ax$,则乘积为 $x^a$。由于 $n$ 为常数,因此当 $x^{\frac{1}{x}}$ 取最大值时,乘积达到最大值:

$$ x^a = x^{\frac{n}{x}} = (x^{\frac{1}{x}})^n $$

问题转化为求 $y = x^{\frac{1}{x}}$ 的极大值,对 $x$ 求导:

$$ \begin{aligned} \ln y & = \frac{1}{x} \ln x & \text{取对数} \ \frac{1}{y} \dot {y} & = \frac{1}{x^2} - \frac{1}{x^2} \ln x & \text{对 $x$ 求导} \ & = \frac{1 - \ln x}{x^2} \ \dot {y} & = \frac{1 - \ln x}{x^2} x^{\frac{1}{x}} & \text{整理得} \end{aligned} $$

令 $\dot {y} = 0$,得 $1 - \ln x = 0$,驻点为 $x_0 = e \approx 2.7$。由导数符号变化可知 $x_0$ 为极大值点:

$$ \dot {y} \begin{cases}

0 & , x \in [- \infty, e) \ < 0 & , x \in (e, \infty] \end{cases} $$

由于切分长度 $x$ 必须为整数,最接近 $e$ 的整数为 $2$ 或 $3$。代入对比:

$$ y(3) = 3^{1/3} \approx 1.44, \quad y(2) = 2^{1/2} \approx 1.41 $$

口算对比技巧:给两数字同时取 $6$ 次方再对比:

$$ [y(3)]^6 = (3^{1/3})^6 = 9 > [y(2)]^6 = (2^{1/2})^6 = 8 $$

推论二:尽可能将绳子以长度 $3$ 等分为多段时,乘积最大。

切分规则与算法流程

综合两条推论,原书给出按优先级排列的切分规则:

  1. 最优:$3$。把绳子尽可能切为多个长度为 $3$ 的片段,留下的最后一段绳子长度可能为 $0$、$1$、$2$ 三种情况;
  2. 次优:$2$。若最后一段绳子长度为 $2$,则保留,不再拆为 $1+1$;
  3. 最差:$1$。若最后一段绳子长度为 $1$,则应把一份 $3 + 1$ 替换为 $2 + 2$,因为 $2 \times 2 > 3 \times 1$。

据此,算法流程分为两个分支(设求余操作符号为 "$\odot$"):

分支 1:$n \leq 3$。按照规则本应不切分,但题目要求必须剪成 $m > 1$ 段,因此必须剪出一段长度为 $1$ 的绳子,直接返回 $n - 1$(即cuttingRope(2) = 1cuttingRope(3) = 2)。

分支 2:$n > 3$。求 $n$ 除以 $3$ 的整数部分 $a$ 和余数部分 $b$(即 $n = 3a + b$),分三种情况处理:

余数 $b$切分形态返回结果
$0$$a$ 段长度为 $3$$3^a \odot 1000000007$
$1$一个 $1+3$ 转换为 $2+2$$(3^{a-1} \times 4) \odot 1000000007$
$2$$a$ 段长度为 $3$,另留一段 $2$$(3^a \times 2) \odot 1000000007$

以仓库测试用例n = 10验证:$10 = 3 \times 3 + 1$,触发 $b=1$ 情形,切分为 $3 + 3 + 4$,乘积 $3 \times 3 \times 4 = 36$,三种语言实现的驱动代码均以该输入运行并输出36(见下文源码部分)。

大数求余:本题真正的难点

为什么需要大数求余

  • 大数越界:当 $a$ 增大时,最终返回的 $3^a$ 以指数级别增长,可能超出int32甚至int64的取值范围,导致返回值错误;
  • 大数求余问题:在仅使用int32类型存储的前提下,正确计算 $x^a$ 对 $p$ 求余(即 $x^a \odot p$)的值;
  • 解决方案:循环求余、快速幂求余。后者时间复杂度更低,两种方法均基于以下求余运算规则推出:

$$ (xy) \odot p = [(x \odot p)(y \odot p)] \odot p $$

方法一:循环求余(时间复杂度 O(N))

根据求余运算性质推出(因为本题中 $x < p$,所以 $x \odot p = x$):

$$ x^a \odot p = [(x ^{a-1} \odot p)(x \odot p)] \odot p = [(x ^{a-1} \odot p)x] \odot p $$

利用此公式,可通过循环依次求 $x^1, x^2, ..., x^{a-1}, x^a$ 对 $p$ 的余数,保证每轮中间值rem都在int32取值范围中:

# 求 (x^a) % p —— 循环求余法 def remainder(x, a, p): rem = 1 for _ in range(a): rem = (rem * x) % p return rem

时间复杂度 $O(N)$,其中 $N = a$,为循环的线性复杂度。

方法二:快速幂求余(时间复杂度 O(log N))

根据求余运算性质可推出:

$$ x^a \odot p = (x^2)^{a/2} \odot p = (x^2 \odot p)^{a / 2} \odot p $$

当 $a$ 为奇数时 $a/2$ 不是整数(//代表向下取整除法),分两种情况:

$$ {x^a \odot p = } \begin{cases} (x^2 \odot p)^{a // 2} \odot p & \text{, $a$ 为偶数} \ {[(x \odot p)(x ^{a-1} \odot p)] \odot p = [x(x^2 \odot p)^{a//2}] \odot p} & \text{, $a$ 为奇数} \end{cases} $$

核心思想是:每次把指数问题从 $a$ 降低至 $a//2$,只需循环 $\log_2 N$ 次,复杂度降为对数级别。原书封装的参考方法如下:

# 求 (x^a) % p —— 快速幂求余 def remainder(x, a, p): rem = 1 while a > 0: if a % 2: rem = (rem * x) % p x = x ** 2 % p a //= 2 return rem

帮助理解(原书示例表格):初始状态 $rem=1, x=3, a=19, p=1000000007$,循环将 $rem \times (x^a \odot p)$ 逐步化为 $rem \times (x^0 \odot p) = rem \times 1$ 的形式,即rem为余数答案:

$n$$rem \times (x^a \odot p)$$rem_n=rem_{n-1} \times x_{n-1} \odot p$$x_n=x_{n-1}^2 \odot p$$a_n=a_{n-1}//2$
$1$$1 \times (3^{19} \odot p)$$1$$3$$19$
$2$$3 \times (9^{9} \odot p)$$3=1\times3\odot p$$9=3^2 \odot p$$9=19//2$
$3$$27 \times (81^{4} \odot p)$$27 = 3 \times 9 \odot p$$81=9^2\odot p$$4=9//2$
$4$$27 \times (6561^{2} \odot p)$$27$$6561=81^2 \odot p$$2=4//2$
$5$$27 \times (43046721^{1} \odot p)$$27$$43046721=6561^2 \odot p$$1=2//2$
$6$$162261460 \times (175880701^{0} \odot p)$$162261460=27 \times 43046721 \odot p$$175880701=43046721^2 \odot p$$0=1//2$

参考实现:三种语言的完整代码

原书特别指出了语言差异这一工程细节:

  • Python:由于语言特性,理论上变量取值范围由系统内存大小决定(无限大),其实不需要考虑大数越界问题;
  • Java / C++:根据快速幂计算原理,至少要保证变量xrem可以正确存储 $1000000007^2$,而 $2^{64} > 1000000007^2 > 2^{32}$,因此必须选取long类型(long为 64 位),乘法中间量才不会溢出。

Python:快速幂求余版

这是原书主推的"带求余过程"写法(仓库实现:sfo_14ii_cut_the_rope_ii_s1.py)。一个值得注意的实现技巧是:循环的指数取a = n // 3 - 1(比数学推导少 1 个 3),把最后一段 $3^1$ 留到循环外与余数部分合并处理——这样循环结束后rem = 3^{n//3 - 1} % p,三种余数情形分别再乘346即可:

class Solution: def cuttingRope(self, n: int) -> int: if n <= 3: return n - 1 a, b, p, x, rem = n // 3 - 1, n % 3, 1000000007, 3 , 1 while a > 0: if a % 2: rem = (rem * x) % p x = x ** 2 % p a //= 2 if b == 0: return (rem * 3) % p # = 3^(a+1) % p if b == 1: return (rem * 4) % p # = 3^a * 4 % p return (rem * 6) % p # = 3^(a+1) * 2 % p

仓库中该文件的驱动代码以n = 10为测试用例,可直接运行验证输出36

Python:利用大整数特性的简化版

仓库另一实现(sfo_14ii_cut_the_rope_ii_s2.py)利用 Python 大整数直接3 ** a后取余,逻辑与"切分规则"一一对应,可读性更强:

# 由于语言特性,Python 可以不考虑大数越界问题 class Solution: def cuttingRope(self, n: int) -> int: if n <= 3: return n - 1 a, b, p = n // 3, n % 3, 1000000007 if b == 0: return 3 ** a % p if b == 1: return 3 ** (a - 1) * 4 % p return 3 ** a * 2 % p

Java / C++:long 防溢出版

两语言实现几乎逐行一致(仓库实现:Java 版、C++ 版),核心是long rem = 1, x = 3两个 64 位变量承载x * x(最大约 $10^{18} < 2^{63}$)这类中间乘积:

class Solution { public int cuttingRope(int n) { if(n <= 3) return n - 1; int b = n % 3, p = 1000000007; long rem = 1, x = 3; for(int a = n / 3 - 1; a > 0; a /= 2) { if(a % 2 == 1) rem = (rem * x) % p; x = (x * x) % p; } if(b == 0) return (int)(rem * 3 % p); if(b == 1) return (int)(rem * 4 % p); return (int)(rem * 6 % p); } }
class Solution { public: int cuttingRope(int n) { if(n <= 3) return n - 1; int b = n % 3, p = 1000000007; long rem = 1, x = 3; for(int a = n / 3 - 1; a > 0; a /= 2) { if(a % 2 == 1) rem = (rem * x) % p; x = (x * x) % p; } if(b == 0) return (int)(rem * 3 % p); if(b == 1) return (int)(rem * 4 % p); return (int)(rem * 6 % p); } };

从源码结构看,Java 与 C++ 版本均使用for (int a = n / 3 - 1; a > 0; a /= 2)的 for 循环形态等价替代 Python 的while快速幂骨架,语义完全一致。

复杂度分析

以下为快速幂(二分求余)法的复杂度结论,与原书一致:

  • 时间复杂度 $O(\log_2 N)$:其中 $N = a$。二分法为对数级别复杂度,每轮仅有求整、求余、次方运算。原书引用公开资料认为:不超过机器字长的整数的求整/求余运算可视为 $O(1)$,整型取幂(在快速幂中退化为单次乘法)同理视为 $O(1)$;
  • 空间复杂度 $O(1)$:变量a, b, p, x, rem使用常数大小的额外空间。

小结与延伸阅读

本题的解题链条可以概括为:均值不等式证明等分最优 → 求导得到 $e \approx 2.7$ → 整数约束下比较 $2^{1/2}$ 与 $3^{1/3}$ 锁定段长 3 → 按余数 0/1/2 处理尾段(3+1 → 2+2的关键替换)→ 快速幂取模保证大数安全。其中 $b = 1$ 时"借一个 3 凑成 2+2"的处理是本算法最容易出错的细节,建议结合 $n \in {2, 3, 4, 5, 7, 10}$ 等边界样例逐一验证。

在 LeetCode-Book 仓库中,本篇的完整解析文档位于 sword_for_offer/docs/剑指 Offer 14- II. 剪绳子 II.md,前置题目 剑指 Offer 14- I. 剪绳子 的数学推导部分与本文同源,可作为对照阅读;三种语言的完整可运行实现分别存放在sword_for_offer/codes/python/sword_for_offer/codes/java/sword_for_offer/codes/cpp/目录下,均附带n = 10的驱动测试代码,可直接编译运行复核上述推导。

【免费下载链接】LeetCode-Book《剑指 Offer》《图解算法数据结构》《Krahets 笔面试精选 88 题》Python, Java, C++ 解题代码项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Book

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 12:32:00

AI五阶进化:从工具到创造性伙伴的技术路径

1. 从工具到伙伴&#xff1a;AI应用的五阶进化论第一次接触ChatGPT时&#xff0c;我像大多数人一样把它当作高级搜索引擎使用。直到某个深夜&#xff0c;当AI助手在我调试代码时主动指出潜在的内存泄漏问题&#xff0c;才意识到人机协作正在经历范式转移。这个五阶模型源于三年…

作者头像 李华
网站建设 2026/9/16 12:31:29

MATLAB实现无人机三维路径规划的差分进化算法

1. 项目背景与核心价值无人机三维路径规划是当前智能飞行器领域的核心技术难点之一。传统算法在复杂地形和动态障碍物环境下往往表现不佳&#xff0c;而差分进化算法&#xff08;Differential Evolution, DE&#xff09;凭借其强大的全局搜索能力和自适应特性&#xff0c;成为解…

作者头像 李华
网站建设 2026/9/16 12:30:29

Vue3+ThreeJS实现机械臂3D实时预览与正向运动学

简介&#xff1a;本资源是一套基于Vue3与Three.js开发的3D机械臂可视化控制项目&#xff0c;面向计算机、自动化、人工智能等专业的在校学生、教师及初学者&#xff0c;解决三维交互式机械臂建模、关节角度实时控制与视角切换等核心学习难点。压缩包共18个文件&#xff0c;含5个…

作者头像 李华
网站建设 2026/9/16 12:29:13

Flutter与鸿蒙结合的跨平台游戏开发实践

1. 项目背景与核心价值最近在技术社区看到不少关于鸿蒙与Flutter结合的讨论&#xff0c;作为一个长期关注跨平台开发的工程师&#xff0c;我决定动手实现一个"智力迷宫挑战"的Demo来验证这套技术栈的可行性。这个项目本质上是通过Flutter框架开发游戏逻辑&#xff0c…

作者头像 李华
网站建设 2026/9/16 12:28:37

RK3568驱动SPI小屏实战:从FrameBuffer到fbtft的嵌入式Linux显示方案

1. 项目背景与方案选型&#xff1a;为什么用RK3568驱动一块SPI小屏做嵌入式Linux开发这些年&#xff0c;接触过不少显示方案。去年接了个工控HMI面板的小项目&#xff0c;主控选了瑞芯微RK3568&#xff0c;屏幕却是一块几英寸的SPI接口LCD。很多人一听就皱眉&#xff1a;RK3568…

作者头像 李华
网站建设 2026/9/16 12:27:08

YOLOv8与YOLO11在红外输电线路过热检测中的对比与部署实践

简介&#xff1a;面向电力巡检与目标检测学习者的输电线路过热检测系统完整方案&#xff0c;基于YOLO11与YOLOv8双框架实现从数据标注、模型训练到推理部署的全流程。压缩包共2000个文件&#xff0c;以txt标注数据、md说明文档、py训练与推理脚本、yaml模型配置为主&#xff0c…

作者头像 李华