剑指 Offer 14- I. 剪绳子 [中等]
题目描述
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m-1]
。请问 k[0]*k[1]*...*k[m-1]
可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
示例:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
限制:
2 <= n <= 58
解题思路:
贪心思路:
设一绳子长度为 n ( n>1 ),则其必可被切分为两段
n=n_1+n_2
。
根据经验推测,切分的两数字乘积往往原数字更大,即往往有n_1 × n_2 > n_1 + n_2 = n
例如绳子长度为 6 :
6 = 3 + 3 < 3 × 3 = 9
;
也有少数反例,例如 2 :2 = 1 + 1 > 1 × 1 = 1
。
推论一:
合理的切分方案可以带来更大的乘积。
设一绳子长度为 n (n>1 ),切分为两段 n=n_1+n_2
,切分为三段 n=n_1+n_2+n_3
。
根据经验推测,三段 的乘积往往更大,即往往有 n_1 × n_2 × n_3 > n_1 × n_2
。
- 例如绳子长度为 9 : 两段
9=4+5
和 三段9=3+3+3
,则有4×5<3×3×3
。 - 也有少数反例,例如 6 : 两段
6=3+3
和 三段6=2+2+2
,则有3×3>2×2×2
。
推论二:
若切分方案合理,绳子段切分的越多,乘积越大。
总体上看,貌似长绳子切分为越多段乘积越大,但其实到某个长度分界点后,乘积到达最大值,就不应再切分了。
问题转化: 是否有优先级最高的长度 x 存在?若有,则应该尽可能把绳子以 x 长度切为多段,以获取最大乘积。
推论三:
为使乘积最大,只有长度为 2 和 3 的绳子不应再切分,且 3 比 2 更优 (详情见下表) 。
绳子切分方案 | 乘积 | 结论 |
---|---|---|
2=1+1 | 1×1=1 | 2 不应切分 |
3=1+2 | 1×2=2 | 3 不应切分 |
4=2+2=1+3 | 2×2=4>1×3=3 | 4 和 2 等价,且2+2 比 1+3 更优 |
5=2+3=1+4 | 2×3=6>1×4=4 | 5 应切分为 2+3 |
6=3+3=2+2+2 | 3×3=9>2×2×2=8 | 6 应切分为3+3 ,进而推出 3 比 2 更优 |
>7 | … | 长绳(长度>7)可转化为多个短绳(长度1~6),因此肯定应切分 |
结论:尽可能把绳子分成长度为3的小段,这样乘积最大
复杂度分析:
- 时间复杂度:O(n)
- 空间复杂度:O(1)
代码:
package main
import "fmt"
func cuttingRope(n int) int {
if n < 4 {
return n-1
}
res :=1
for n>4 {
res *=3
n -= 3
}
return res * n // 乘以剩余的数
}
func main(){
fmt.Println(cuttingRope(1))
fmt.Println(cuttingRope(2))
fmt.Println(cuttingRope(3))
fmt.Println(cuttingRope(4))
fmt.Println(cuttingRope(5))
fmt.Println(cuttingRope(6))
}