1. 程式人生 > >Leetcode 343:整數拆分(最詳細的解法!!!)

Leetcode 343:整數拆分(最詳細的解法!!!)

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

示例 1:

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

示例 2:

輸入: 10
輸出: 36
解釋: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。

說明: 你可以假設 n 不小於 2 且不大於 58。

解題思路

這個問題很簡單,可以通過遞迴解決。舉個列子,對於正整數4,我們要知道將4拆分為哪幾個正整數的和,並使這些整數的乘積最大化,那我們只要知道3、2、1的乘積最大化分別是多少,然後從中選出最大值即可,以此類推下去即可。

class Solution
: def integerBreak(self, n): """ :type n: int :rtype: int """ if n == 1: return 1 result = -1 for i in range(1, n): result = max(result, i*(n - i), i * self.integerBreak(n - i)) return result

很多人在寫上面這個程式碼的時候會將

max(result, i*(n - i), i * self.integerBreak(n - i)) ==>
max(result, i * self.integerBreak(n - i))

寫成下面的那種形式,關鍵問題在於沒有理解函式的定義。integerBreak是將一個正整數拆分為至少兩個正整數的和,也就是說i * self.integerBreak(n - i)至少是三個整數的積,那麼我們在比較最大值的時候自然要將i*(n - i)給加上啦。

上面這種演算法中存在著大量的重複運算(在哪裡呢?)。我們可以通過記憶化搜尋的方式來優化上面的問題。

class Solution
: def integerBreak(self, n): """ :type n: int :rtype: int """ if n == 1: return 1 mem = [-1 for i in range(n + 1)] mem[1] = 1 for i in range(2, n + 1): for j in range(1, i): mem[i] = max(mem[i], j*(i - j), j*mem[i - j]) return mem[n]

實際上這個問題通過數學方法很快就可以解決。我們將一個整數拆分,往往這個拆分後的數中包含整數的話,那麼這些數的乘積最大

2^3 < 3^2
4^3 < 3^4 > 3^3^1
5^3 < 3^5 < 3^3^2
6^3 < 3^6 < 3^3^3

通過上面的例子你會發現這樣的規律:想要乘積最大,那麼一定要將3作為基底,並且<=4的餘數不再進行拆分。為什麼會這樣呢?

我們將n拆分成nx\frac {n}{x}個數值為x的整數,那麼這些整數的乘積就是xn/xx^{n/x},我們現在的目標就是計算max(xn/x)max(x^{n/x})。我們對這個函式求導,得到

  • δxnxδx=nxnx2(1ln(x))\frac{\delta{x^{\frac{n}{x}}}}{\delta x}=n*x^{\frac{n}{x} -2}*(1-ln(x))

x=e的時候取最大值。所以我們這裡可以取的整數就是23,但是

6 = 2 + 2 + 2 = 3 + 3 but
2*2*2 < 3*3

所以就有了前面的結論。但是這裡還有一個問題,就是為什麼要取相同數的乘積,不同數不行嗎?確實不行(~o ̄3 ̄)~

基於上述的論點,我們可以寫出這樣的程式碼,非常的簡潔。

class Solution:
    def integerBreak(self, n):
        """
        :type n: int
        :rtype: int
        """  
        if n <= 3:
            return n - 1
        
        result = 1
        while n > 4:
            n -= 3
            result *= 3

        return n * result

如有問題,希望大家指出!!!