1. 程式人生 > 實用技巧 >LeetCode 343. 整數拆分 | Python

LeetCode 343. 整數拆分 | Python

343. 整數拆分


題目來源:力扣(LeetCode)https://leetcode-cn.com/problems/integer-break

題目


給定一個正整數 n,將其拆分為至少兩個正整數的和,並使這些整數的乘積最大化。 返回你可以獲得的最大乘積。

示例 1:

輸入: 2
輸出: 1
解釋: 2 = 1 + 1, 1 × 1 = 1。

示例 2:

輸入: 10
輸出: 36
解釋: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。
說明: 你可以假設 n 不小於 2 且不大於 58。

解題思路


思路:推導

這裡,我們用數學推導的方法,來嘗試解決這個問題。先審題,題目中要我們將數字 n 進行拆分,然後求拆分後數字乘積最大值。這裡需要注意:題目中說明 n 至少拆分為兩個正整數。

由前面所述的題意,我們可知,目前的問題就是如何拆分,才能使得乘積最大?這裡舉個例子,若將 1 個數拆分為 2 個數求乘積(均為正整數),我們知道,拆分的兩個數字越接近,那麼乘積會越大。比較符合前面這段話的有:求矩形面積。我們知道,矩形當中,正方形面積最大。

那麼,我們先按照這個思路往下思考(後續在推導)。前面舉例說的拆分為 2 個數字,這道題當中說的是至少兩個正整數,那麼按照前面所得出的推論,將數字 n 進行拆分,可能會出現以下情況:

  • 數字 n 進行拆分時,不一定均分(因為題目要的拆分之後還是正整數)
  • 當上面的情況一定存在時,那麼將數字 n 進行拆分時,拆分的數字應該為多少最合適。

上面的情況中,第一種比較好理解。第二種情況,這裡舉個例子,例如給定的數字 n 為 6,那麼:

# ans 在這裡表示乘積
n = 6 = 3 + 3
ans = 3 * 3 = 9

或者

n = 6 = 2 + 2 + 2
ans = 2 * 2 * 2 = 8

上面都是將數字 n 拆分為相同的正整數,只是拆分數量不同,但是兩者的乘積會有差距。那麼這裡也就還有個問題,將給定的數字 n 進行拆分時,要拆分為哪個正整數,最終的乘積為最大。

由前面的分析,我們知道,現在的主要的問題有兩個:

  1. 如何證明拆分的數字相等,乘積最大?
  2. 數字 n 拆分為哪個數字,最終的乘積最大?

先看第一個問題,這裡直接引用 "算術-幾何均值不等式",公式如下:

$$
\frac{x_1+x_2+...+x_a}{a} \geq \sqrt[a]{x_1x_2...x_a}
$$

上面的式子中,當且僅當 $x_1=x_2=...=x_a$ 的時候,等號成立。

這裡,我們得到:當確定拆分數量 a,那麼拆分的數字相同時,乘積最大。

現在看第二個問題,根據前面的結論,設拆分數量為 a,拆分數字為 x,也即是 n=ax,那麼乘積為 $x^a$。現在,則是要求得什麼情況下 $x^a$ 取得最大值?證明如下:

  • 對 $x^a$ 公式進行轉換

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

在這裡,n 為常數且不小於 2,那麼冪函式中,此時,底數越大,值越大。也就是要求得 $x^{\frac{1}{x}}$ 的最大值。

  • 那麼現在的問題就是要求得 $f(x)=x^{\frac{1}{x}}$ 的最大值。先對兩邊取對數:

$$
ln(f(x)) = \frac{1}{x}lnx
$$

  • 再對兩邊進行求導:

$$
\begin{aligned}
\frac{1}{f(x)}·f{\prime}(x)&=(\frac{1}{x}){\prime}lnx + \frac{\frac{1}{x}}{x}·(x)^{\prime} \
f{\prime}(x)&=\left[(\frac{1}{x}){\prime}lnx + \frac{\frac{1}{x}}{x}·(x)^{\prime}\right]·f(x)\
f{\prime}(x)&=\left[-\frac{1}{x2}lnx+\frac{1}{x2}\right]·x{\frac{1}{x}}\
f{\prime}(x)&=\frac{1-lnx}{x2}·x^{\frac{1}{x}}
\end{aligned}
$$

  • 令 $f^{\prime}(x)=0$,也就是 $1-lnx=0$,那麼此時,可得臨界點為:$x=e$。現在此時 $x$ 是否是極值:

$$
f^{\prime}=
\begin{cases}
值大於 0,x\in[-\infty, e) \
值小於 0,x\in(e, +\infty]
\end{cases}
$$

由此可以判斷 $x=e$ 時,取得極大值。我們知道自然常數 $e$ 約等於 2.7。題目要求拆分的是正整數,那麼我們將 2 和 3 分別代入函式 $f(x)$ 當中,得:

$$
\begin{aligned}
f(2)=2^{\frac{1}{2}}\approx 1.41 \
f(3)=3^{\frac{1}{3}}\approx 1.44
\end{aligned}
$$

我們可以看到當 x 取 3 的時候,乘積達到最大。

現在上面提出的兩個問題已經得以證明。但還有一個需要注意的,前面提到的情況中,提及拆分的時候可能出現不均分的情況。

現在確定拆分的數字為 3 的情況下,乘積可取最大值。那麼此時拆分的時候,可能出現的餘數會有 0,1,2。現在就這個情況下,逐個分析(下面的情況是針對 n > 3 的情況),令 n = ax + b,x 確定取 3,值可最大,也就是 n = 3a + b,那麼會出現的情況如下:

  • b = 0,表示均分,此時直接返回 3^a;
  • b = 1,這裡需要注意。這裡需要重新合併拆分,當剩下 1 時,3 x 1 < 2 x 2,所以將其中一個 3 與餘數 1,重新結合拆分為 2 和 2,那麼最終返回 3^(a-1) x 2 x 2;
  • b = 2,返回 3^a x 2。

這裡在說下 n <= 3 的情況,不拆分的結果更優。當時題目要求必須拆分至少兩個整數,那麼此時拆出一個數值 1,那麼最終返回 n - 1。

具體的程式碼實現如下。

程式碼實現


class Solution:
    def integerBreak(self, n: int) -> int:
        # 先處理 n 小於或等於 3 的情況
        if n <= 3:
            return n-1
        
        # 先拆分,確定數量以及餘數
        a = n // 3
        b = n % 3

        # 這裡呼叫 math.pow() 方法,該方法呼叫底層 c 庫的 pow 函式,效率更高
        import math
        # 均分直接返回 3^a
        if b == 0:
            return int(math.pow(3, a))
        elif b == 1:
            return int(math.pow(3, a-1) * 4)
        # 剩下就是餘 2 的情況
        return int(math.pow(3, a) * 2)

實現結果


歡迎關注


公眾號 【書所集錄