题目描述
思路解析
一句话答案:LeetCode 343 整数拆分求把 n 拆成至少两段的最大乘积。动态规划:dp[i] 枚举第一段 j,在 j×(i-j) 停拆和 j×dp[i-j] 续拆间取大,因剩段可拆可不拆,时间 O(n²)、空间 O(n)。
整数拆分这道题到底要最大化什么
给一个正整数 n,把它拆成至少两个正整数相加 n=a1+a2+…+ak(k≥2),让这些数的乘积 a1×a2×…×ak 最大,返回这个最大乘积。题面例子 n=10,最好的拆法是 3+3+4,乘积 3×3×4=36,就是答案。这里必须拆成两段以上,不能原样留一个 n 不动。
为什么枚举所有拆法会指数爆炸
最直接的想法是把所有拆法都列出来比乘积。可 n 能拆成两段、三段、更多段,每段还能取不同大小,方案随 n 指数级膨胀,稍大就列不完。而且很多拆法共用同一截尾巴——拆 10 时剩下的 8 怎么拆,和单独拆 8 是同一个子问题,被反复重算。把「拆 i 能得的最大乘积」算一次存下来复用,指数枚举就压成动态规划(把子问题算过的结果存下、后面直接取用)。
dp[i] 定成什么,dp[1] 为什么要设 1
定义 dp[i] 为「把整数 i 拆成至少两段能得到的最大乘积」,答案就是 dp[n]。下标 i 是被拆的数、格子里存它拆开后的最大积。开头 dp[1]=1 是个约定:1 没法再拆成两个正整数,本不该有值,但它会在别人「续拆」时被当乘数用,设成 1 表示「乘上这一段不改变积」。别把 dp[1] 设成 0,否则任何用到它的续拆方案都会被乘成 0,整条转移崩掉。
为什么每格要在拆到底和再拆一次之间取大
算 dp[i] 时枚举第一段取多大,记作 j,从 1 扫到 i-1。第一段定成 j 之后,剩下的 i-j 有两条路:一是就让它整段留着不再拆,这段贡献是 i-j 本身,得 j×(i-j);二是把 i-j 也拆到最优,贡献是 dp[i-j],得 j×dp[i-j]。两条取大、再对所有 j 取大,就是 dp[i]=max(j×(i-j), j×dp[i-j])。
为什么非得留「不拆」这一路?因为 dp[i-j] 是「至少拆两段」的积,有时反而更小。比如剩下 3,拆成 1+2 积才 2,还不如整个 3 留着。少了 j×(i-j) 这支,小段被强行拆开就会算亏。
拿 n=10 亲手把这张 dp 表填一遍
从小往大填。dp[1]=1 打底,dp[2] 只能 1+1 得 1。dp[3] 整段留着 1×2=2 胜过续拆。dp[4] 第一段取 2,2×2=4。dp[5] 第一段取 2,2×3=6。dp[6] 第一段取 3,3×3=9。到 dp[7] 续拆更划算,第一段 2 接续拆的 2×dp[5]=2×6=12。dp[8] 得 2×dp[6]=2×9=18,dp[9] 得 3×dp[6]=3×9=27,dp[10] 得 2×dp[8]=2×18=36。dp[10]=36,正是 3+3+4 那组,和题面对上。
复杂度是多少,n=2 和 n=3 两个边界怎么收
外层枚举被拆的数 i、内层枚举第一段 j,两层循环 O(n²);一维 dp 数组 O(n) 空间。两个小边界要想清:n=2 只能拆 1+1,dp[2]=1,别被「乘积该更大」的直觉带偏;n=3 最优是 1+2 得 2,不是硬凑更多段。还有内层 j 只能取到 i-1,得保证至少拆成两段,写成取到 i 就等于允许「原样不拆」,会把 dp[i] 顶成 i 而算错。
▶ 动画逐步走查(文字版)——想跟着上方动画一帧帧对照就展开
记住这条转移式——每一格都在套它,枚举第一段、剩下要么停要么续拆。
上行是下标 i(固定参照),下行 dp 待填。i=1 没法再拆,约定 dp[1]=1 当作「续拆」的基。
1 无法拆成两个正整数,把 dp[1]=1 作为基准——它只在被别人「续拆」时当乘数用。
算 dp[2]:枚举第一段 j 后最划算的是 j=1 —— 第一段 1、剩下 1 不拆,1×1=1(在 1..1 里它最大)。
把 1 填进 dp[2]。剩下那段不拆更值。
算 dp[3]:枚举第一段 j 后最划算的是 j=1 —— 第一段 1、剩下 2 不拆,1×2=2(在 1..2 里它最大)。
把 2 填进 dp[3]。剩下那段不拆更值。
算 dp[4]:枚举第一段 j 后最划算的是 j=2 —— 第一段 2、剩下 2 不拆,2×2=4(在 1..3 里它最大)。
把 4 填进 dp[4]。剩下那段不拆更值。
算 dp[5]:枚举第一段 j 后最划算的是 j=2 —— 第一段 2、剩下 3 不拆,2×3=6(在 1..4 里它最大)。
把 6 填进 dp[5]。剩下那段不拆更值。
算 dp[6]:枚举第一段 j 后最划算的是 j=3 —— 第一段 3、剩下 3 不拆,3×3=9(在 1..5 里它最大)。
把 9 填进 dp[6]。剩下那段不拆更值。
算 dp[7]:枚举第一段 j 后最划算的是 j=2 —— 第一段 2、剩下 5 继续最优拆 dp[5]=6,2×6=12(在 1..6 里它最大)。
把 12 填进 dp[7]。剩下那段继续拆更值。
算 dp[8]:枚举第一段 j 后最划算的是 j=2 —— 第一段 2、剩下 6 继续最优拆 dp[6]=9,2×9=18(在 1..7 里它最大)。
把 18 填进 dp[8]。剩下那段继续拆更值。
算 dp[9]:枚举第一段 j 后最划算的是 j=3 —— 第一段 3、剩下 6 继续最优拆 dp[6]=9,3×9=27(在 1..8 里它最大)。
把 27 填进 dp[9]。剩下那段继续拆更值。
算 dp[10]:枚举第一段 j 后最划算的是 j=2 —— 第一段 2、剩下 8 继续最优拆 dp[8]=18,2×18=36(在 1..9 里它最大)。
把 36 填进 dp[10]。剩下那段继续拆更值。
最右 dp[10]=36 就是把 10 拆开能得到的最大乘积(对应 10=3+3+4,3×3×4=36)。
小 n 边界先想清,n=2/3 受「至少两段」约束。
两个高频追问。
参考代码
def integerBreak(n): dp = [0] * (n + 1) dp[1] = 1 for i in range(2, n + 1): for j in range(1, i): dp[i] = max(dp[i], j * (i - j), j * dp[i - j]) return dp[n]复杂度
- 时间:O(n²),每个 i 枚举 j=1..i-1
- 空间:O(n),一维 dp 数组
易错点
面试追问把动画讲成自己的话
追问为什么 dp[1]=1 而不是 0?
追问有没有 O(1) 数学解?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
统计各位数字都不同的数字个数
LeetCode 357 · 中等 · 沿着 动态规划套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题