# 14.II.剪绳子

## 一、 题目

给你一根长度为 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。

答案需要取模 1e9+7（1000000007），如计算初始结果为：1000000008，请返回 1。

示例 1：

```swift
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
```

示例 2:

```swift
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
```

提示：

2 <= n <= 1000

注意：本题与[主站 343 题](https://leetcode-cn.com/problems/integer-break/)相同

来源：[力扣（LeetCode）](https://leetcode-cn.com/problems/jian-sheng-zi-ii-lcof)

著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。

## 二、 常规思路与问题

咋一看，和[14-I.剪绳子](https://ryukiedev.gitbook.io/wiki/shu-ju-jie-gou-yu-suan-fa/jian-zhi-offerswift/14i.-jian-sheng-zi)没什么区别嘛～ 不就是结果要取模嘛～

### 2.1 一般思路

***那你就上当了***

我们用动态规划的解法来试试：

```swift
func cuttingRope(_ n: Int) -> Int {
    if n < 4 {
        return n - 1
    }
    var dp = [Int](repeating: 1, count: n + 1)
    for i in 3...n {
        for j in 2..<i {
            dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j]))
        }
    }
    return dp[n] % 1_000_000_007
}
```

试试两个测试用例：

```
10 - 36
2 - 1
```

***很正常没问题嘛～🤷‍♂️***

### 2.2 试试120

![120](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F0dda250155b6336bb4124ee298fb63d1a723ee09.png?generation=1627320187643949\&alt=media)

***居然出错了？***

### 2.3 数据长度问题

请注意本题的数据范围 `2 <= n <= 1000` 和第一题的 `2 <= n <= 58`，差别很大。

上面报错就是因为数据超出了范围了。

> 那你可能会想到，那我换 `Int64` ？我换 `long` ？

然鹅并不行

> 那我每次纯数据都取一下模？

更不行，中间数据都变了，结果当然更错

#### 我们用 `Decimal` 来接收看看`120`时到底是多大：

![12157665459056928801](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fcbfee7c3cdc6ae953e74edfb4c3efc2af711c489.png?generation=1627320182666439\&alt=media)

`120` 时就已经 `20位` 了，更何况本题的范围最大`1000`。

> 我们就不要再数据类型的选择上进行纠结了

### 2.4 `Decimal` 解法

这里将元素使用 `Decimal` 进行承载

```swift
let base = NSDecimalNumber(decimal: Decimal(1_000_000_007))

func cuttingRope(_ n: Int) -> Int {
    if n < 4 {
        return n - 1
    }

    var dp = [Decimal](repeating: 1, count: n + 1)
    for i in 3...n {
        for j in 2..<i {
            dp[i] = max(dp[i], max(Decimal(j) * Decimal(i - j), Decimal(j) * dp[i - j]))
        }
    }

    let handler = NSDecimalNumberHandler(roundingMode: .down, scale: 0, raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: false)

    let result = NSDecimalNumber(decimal: dp[n])
    let baseCount = result.dividing(by: base).rounding(accordingToBehavior: handler)

    let tp = result.subtracting(base.multiplying(by: baseCount))

    return Int(truncating: tp)
}
```

![Decimal](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F5754c48f66658aa8a69440caeb1efd3f0be39373.png?generation=1627320176129968\&alt=media)

![Decimal](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F297c64b550d322bc668ff0925999f358573ba3a0.png?generation=1627320176803178\&alt=media)

结果到 255 的时候还是错了，还是丢失了精度。毕竟 `NSDecimalNumber` 也是有范围的

![Decimal](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Ff55216555813c3c74cbabbfe2bc37075cf72c32e.png?generation=1627320177147903\&alt=media)

> 感觉想继续用动态规划不太现实了

### 2.5 补充 Java 的 BigInteger 可以动态规划

```swift
import java.math.BigInteger;
class Solution {
    public int cuttingRope(int n) {
        BigInteger[] dp = new BigInteger[n + 1];
         Arrays.fill(dp, BigInteger.valueOf(1));
        // dp[1] = BigInteger.valueOf(1);
        for(int i = 3; i < n + 1; i++){
            for(int j = 1; j < i; j++){
                dp[i] = dp[i].max(BigInteger.valueOf(j * (i - j))).max(dp[i - j].multiply(BigInteger.valueOf(j)));
            }
        }
        return dp[n].mod(BigInteger.valueOf(1000000007)).intValue();
    }
}
```

链接：[作者：ollieq](https://leetcode-cn.com/problems/jian-sheng-zi-ii-lcof/solution/jian-dan-li-jie-dong-tai-gui-hua-xun-hua-4g3o/)

> 这里就不再纠结数据类型了

## 三、 贪心-循环求余

> 尽可能多的切成长度为3的，乘积最大

### 3.1 解法

```swift
func cuttingRope(_ n: Int) -> Int {
    if n < 4 {
        return n - 1
    }
    var res = 1
    var n = n
    while n > 4 {
        res = res * 3 % 1_000_000_007
        n -= 3
    }
    return res * n % 1_000_000_007
}
```

| 时间复杂度 | 空间复杂度 |
| :---: | :---: |
|  O(n) |  O(1) |

![真的是快](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fbd5b4b96c6633950289f29ea6b71bd3c87ad2aa5.png?generation=1627320177331156\&alt=media)

***真快***

### 3.2 循环求余

详细可以移步[这个题解](https://leetcode-cn.com/problems/jian-sheng-zi-ii-lcof/solution/mian-shi-ti-14-ii-jian-sheng-zi-iitan-xin-er-fen-f/)

![循环求余](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F8d63e0fac8d9d56281eb2b719c7efca2c96c9698.png?generation=1627320176427998\&alt=media)

## 四、贪心-快速幂

核心是通过 `快速幂` 算法及模运算规律，控制数据范围。详细了解可以移步[快速幂](https://ryukiedev.gitbook.io/wiki/shu-ju-jie-gou-yu-suan-fa/ji-qiao/03.-kuai-su-mi)

> [LeetCode](https://leetcode-cn.com/problems/jian-sheng-zi-ii-lcof/solution/tan-xin-kuai-su-mi-by-happy-austin9mq-6bxz/)

### 4.1 解法

```swift
func cuttingRope(_ n: Int) -> Int {
    if n < 4 {
        return n - 1
    }
    let a = n / 3, b = n % 3

    if b == 0 {
        return modPow(3, a) % 1_000_000_007
    }
    else if b == 1 {
        return modPow(3, a - 1) * 4 % 1_000_000_007
    }
    else {
        return modPow(3, a) * 2 % 1_000_000_007
    }
}

func modPow(_ x: Int, _ n: Int) -> Int {
    var res = 1
    var x = x
    var n = n

    while n > 0 {
        if n & 1 == 1 {
            res *= x
            res %= 1_000_000_007 // 限制了数据范围
        }
        x *= x
        x %= 1_000_000_007 // 限制了数据范围
        n >>= 1
    }

    return res
}
```

![快速幂](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F831114aa511f4eaf6a9fd317dc9efda7816bc595.png?generation=1627383766207224\&alt=media)
