—

0:00
0:00

NO.007 · 技术笔记 · Leetcode

Leetcode 343 整数拆分

DATE
READ
4 MIN
VIEWS
–

题目概述

Leetcode 343

Given an integer n, break it into the sum of k positive integers, where k >= 2, and maximize the product of those integers. Return the maximum product you can get.

LeetCode 343 示例:n=2 输出 1(1+1),n=10 输出 36(3+3+4)

Constraints:

  • 2 <= n <= 58

思路

根据题意,对于一个给定的正整数 nn , 有:

n=∑i=1kni(1≤k≤n,nk∈Z+)n = \sum_{i=1}^k n_i \qquad (1 \leq k \leq n, \quad n_k \in \boldsymbol{Z^+})

现在需要求:

max(∏i=1kni)max(\prod_{i=1}^k n_i)

注意到这里的任意 nin_i 均为正整数,所以根据均值不等式,有:

n1n2⋯nkk≤n1+n2+⋯+nkkn1n2⋯nk≤(n1+n2+⋯+nkk)k\begin{aligned} \sqrt[k]{n_1 n_2 \cdots n_k} \quad &\leq \quad \frac{n_1 + n_2 + \cdots + n_k}{k} \\[5mm] n_1 n_2 \cdots n_k \quad &\leq \quad \left(\frac{n_1 + n_2 + \cdots + n_k}{k}\right) ^ k \end{aligned}

当且仅当 n1=n2=⋯=nkn_1 = n_2 = \cdots = n_k 时不等式取等号,也即乘积取得最大值。但是在这道题的语境下 nin_i 为正整数,直接取等的话无法保证满足这个条件。不过,我们可以先按照这个思路去求得 kk 取什么值,也即原本的整数 nn 在被均分成多少个数的时候它们的乘积能得到最大值。令:

f(k)=n1n2⋯nk=(nk)k(1≤k≤n)f(k) = n_1 n_2 \cdots n_k = \left(\frac{n}{k}\right)^k \qquad (1 \leq k \leq n)

想要求 f(k)maxf(k)_{max} , 利用对数微分法求导:

logf(k)=klog(nk)=k(logn−logk)f′(k)f(k)=(logn−logk)−1k×k=logn−logk−1f′(k)=(nk)k(logn−logk−1)\begin{aligned} & log f(k) = k log (\frac{n}{k}) = k (log n - log k) \\[3mm] & \frac{f'(k)}{f(k)} = (log n - log k) - \frac{1}{k} \times k = log n - log k - 1 \\[3mm] & f'(k) = \left(\frac{n}{k}\right) ^ k (log n - log k - 1) \end{aligned}

令 f′(k)=0f'(k) = 0 , 有:

logn−logk−1=0logk=logn−1=logn−loge=log(ne)∴k=ne\begin{aligned} & log n - log k - 1 = 0 \\[3mm] & log k = log n - 1 = log n - log e = log \left(\frac{n}{e}\right) \\[3mm] \therefore \quad & k = \frac{n}{e} \end{aligned}

考察 f(k)f(k) 增减性:

1≤k≤ne,f′(k)>0k>ne,f′(k)<0\begin{aligned} & 1 \leq k \leq \frac{n}{e} \quad , \quad f'(k) > 0 \\[3mm] & k > \frac{n}{e} \quad , \quad f'(k) < 0 \end{aligned}

所以 f(k)f(k) 在 k=nek = \large \frac{n}{e} 时取得最大值, 此时每一个乘数的值均为 ee 。但题目里要求乘数必须为正整数,所以这还不是最终的答案。这里虽然没有严谨的证明,但是我们可以近似地认为只要分离出的乘数 nin_i 越接近 ee , 那么最终这些乘数的乘积越大。而最接近 ee 的正整数为3,所以问题似乎就转变成: 将一个正整数尽可能多地分成正整数3的和。

但这样还不对。我们可以设想一下 n=7n = 7 时,如果只是尽可能多地分离出3,那么最终的结果为:

7=3+3+1,3×3×1=97 = 3 + 3 + 1 \quad , \quad 3 \times 3 \times 1 = 9

然而我们还能得到:

7=3+4,3×4=127 = 3 + 4 \quad , \quad 3 \times 4 = 12

显然12是大于9的。这里其实稍加思考便可以得知问题出在了哪。当我们将一个数不断地拆分为3和剩余的部分,那么根据这个数除以3得到的余数,最终会有如下三种情况:

  • 余数为0, 刚好将3分离完的最理想情况,无需再做讨论。
  • 余数为2, 那么最后剩余的部分为2或5 (少分离一个3), 如果考虑与 e≈2.71828e \approx 2.71828 的接近程度,那么显然前者更优。(两种分离方式在倒数第三项为止的乘积相同, 剩下要做的比较便是(2, 3)与5哪一种与 ee 的差值最小, 显然前者更小)
  • 余数为1, 与上一种情况类似,这里最后剩余的部分为(1, 3)或(2, 2), 比较与 ee 的差值的话后者更优。

到这里为止,问题基本就分析完毕了。最后还需考虑一种情况,那便是 n≤3n \leq 3 的时候。这里可以作为特判, 无外乎两种情况:

  • 2 = 1 + 1, 结果为1
  • 3 = 1 + 2, 结果为2

总结一下思路: 在n>4n > 4时,尽可能多地分离出3。

代码实现

  1. 在 n>4n > 4 时,尽可能多地分离出3

    Time complexity: O(n)

    Space complexity: O(1)

    class Solution:
        def integerBreak(self, n: int) -> int:
            if n <= 3:
                return n - 1
    
            ans = 1
            while n > 4:
                ans *= 3
                n -= 3
    
            return ans * n
    Python3 提交结果:运行时间 43 ms,击败 31.74%;内存 16.2 MB,击败 72.79%
  2. 优化, 根据 nn 的值直接可以计算出分离出来的3的个数, 按照其除以3的余数分类讨论即可

    Time complexity: O(logn)

    Space complexity: O(1)

    class Solution:
        def integerBreak(self, n: int) -> int:
            if n <= 3:
                return n - 1
    
            if n % 3 == 0:
                return 3 ** (n // 3)
            elif n % 3 == 1:
                return 3 ** ((n - 4) // 3) * 4
            else:
                return 3 ** ((n - 2) // 3) * 2
                
    Python3 提交结果:运行时间 35 ms,击败 81.19%;内存 16.1 MB,击败 72.79% 注: 乘方运算的时间复杂度为 **O(logn)**

评论COMMENTS

◂ 返回索引