自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

聊聊DP入門(mén)之整數(shù)拆分!

開(kāi)發(fā) 前端
給定一個(gè)正整數(shù) n,將其拆分為至少兩個(gè)正整數(shù)的和,并使這些整數(shù)的乘積最大化。返回你可以獲得的最大乘積。

 整數(shù)拆分

力扣題目鏈接:https://leetcode-cn.com/problems/integer-break

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

示例 1:

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

示例 2:

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

說(shuō)明: 你可以假設(shè) n 不小于 2 且不大于 58。

思路

看到這道題目,都會(huì)想拆成兩個(gè)呢,還是三個(gè)呢,還是四個(gè)....

我們來(lái)看一下如何使用動(dòng)規(guī)來(lái)解決。

動(dòng)態(tài)規(guī)劃

動(dòng)規(guī)五部曲,分析如下:

確定dp數(shù)組(dp table)以及下標(biāo)的含義

dp[i]:分拆數(shù)字i,可以得到的最大乘積為dp[i]。

dp[i]的定義講貫徹整個(gè)解題過(guò)程,下面哪一步想不懂了,就想想dp[i]究竟表示的是啥!

確定遞推公式

可以想 dp[i]最大乘積是怎么得到的呢?

其實(shí)可以從1遍歷j,然后有兩種渠道得到dp[i].

一個(gè)是j * (i - j) 直接相乘。

一個(gè)是j * dp[i - j],相當(dāng)于是拆分(i - j),對(duì)這個(gè)拆分不理解的話,可以回想dp數(shù)組的定義。

那有同學(xué)問(wèn)了,j怎么就不拆分呢?

j是從1開(kāi)始遍歷,拆分j的情況,在遍歷j的過(guò)程中其實(shí)都計(jì)算過(guò)了。那么從1遍歷j,比較(i - j) * j和dp[i - j] * j 取最大的。遞推公式:dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));

也可以這么理解,j * (i - j) 是單純的把整數(shù)拆分為兩個(gè)數(shù)相乘,而j * dp[i - j]是拆分成兩個(gè)以及兩個(gè)以上的個(gè)數(shù)相乘。

如果定義dp[i - j] * dp[j] 也是默認(rèn)將一個(gè)數(shù)強(qiáng)制拆成4份以及4份以上了。

所以遞推公式:dp[i] = max({dp[i], (i - j) * j, dp[i - j] * j});

那么在取最大值的時(shí)候,為什么還要比較dp[i]呢?

因?yàn)樵谶f推公式推導(dǎo)的過(guò)程中,每次計(jì)算dp[i],取最大的而已。

dp的初始化

不少同學(xué)應(yīng)該疑惑,dp[0] dp[1]應(yīng)該初始化多少呢?

有的題解里會(huì)給出dp[0] = 1,dp[1] = 1的初始化,但解釋比較牽強(qiáng),主要還是因?yàn)檫@么初始化可以把題目過(guò)了。

嚴(yán)格從dp[i]的定義來(lái)說(shuō),dp[0] dp[1] 就不應(yīng)該初始化,也就是沒(méi)有意義的數(shù)值。

拆分0和拆分1的最大乘積是多少?

這是無(wú)解的。

這里我只初始化dp[2] = 1,從dp[i]的定義來(lái)說(shuō),拆分?jǐn)?shù)字2,得到的最大乘積是1,這個(gè)沒(méi)有任何異議!

確定遍歷順序

確定遍歷順序,先來(lái)看看遞歸公式:dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));

dp[i] 是依靠 dp[i - j]的狀態(tài),所以遍歷i一定是從前向后遍歷,先有dp[i - j]再有dp[i]。

枚舉j的時(shí)候,是從1開(kāi)始的。i是從3開(kāi)始,這樣dp[i - j]就是dp[2]正好可以通過(guò)我們初始化的數(shù)值求出來(lái)。

所以遍歷順序?yàn)椋?/p>

  1. for (int i = 3; i <= n ; i++) { 
  2.     for (int j = 1; j < i - 1; j++) { 
  3.         dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j)); 
  4.     } 

舉例推導(dǎo)dp數(shù)組

舉例當(dāng)n為10 的時(shí)候,dp數(shù)組里的數(shù)值,如下:

整數(shù)拆分

以上動(dòng)規(guī)五部曲分析完畢,C++代碼如下:

  1. class Solution { 
  2. public
  3.     int integerBreak(int n) { 
  4.         vector<int> dp(n + 1); 
  5.         dp[2] = 1; 
  6.         for (int i = 3; i <= n ; i++) { 
  7.             for (int j = 1; j < i - 1; j++) { 
  8.                 dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j)); 
  9.             } 
  10.         } 
  11.         return dp[n]; 
  12.     } 
  13. }; 
  • 時(shí)間復(fù)雜度:
  • 空間復(fù)雜度:

貪心

本題也可以用貪心,每次拆成n個(gè)3,如果剩下是4,則保留4,然后相乘,但是這個(gè)結(jié)論需要數(shù)學(xué)證明其合理性!

我沒(méi)有證明,而是直接用了結(jié)論。感興趣的同學(xué)可以自己再去研究研究數(shù)學(xué)證明哈。

給出我的C++代碼如下:

  1. class Solution { 
  2. public
  3.     int integerBreak(int n) { 
  4.         if (n == 2) return 1; 
  5.         if (n == 3) return 2; 
  6.         if (n == 4) return 4; 
  7.         int result = 1; 
  8.         while (n > 4) { 
  9.             result *= 3; 
  10.             n -= 3; 
  11.         } 
  12.         result *= n; 
  13.         return result; 
  14.     } 
  15. }; 

時(shí)間復(fù)雜度:

空間復(fù)雜度:

總結(jié)

本題掌握其動(dòng)規(guī)的方法,就可以了,貪心的解法確實(shí)簡(jiǎn)單,但需要有數(shù)學(xué)證明,如果能自圓其說(shuō)也是可以的。

其實(shí)這道題目的遞推公式并不好想,而且初始化的地方也很有講究,我在寫(xiě)本題的時(shí)候一開(kāi)始寫(xiě)的代碼是這樣的:

  1. class Solution { 
  2. public
  3.     int integerBreak(int n) { 
  4.         if (n <= 3) return 1 * (n - 1); 
  5.         vector<int> dp(n + 1, 0); 
  6.         dp[1] = 1; 
  7.         dp[2] = 2; 
  8.         dp[3] = 3; 
  9.         for (int i = 4; i <= n ; i++) { 
  10.             for (int j = 1; j < i - 1; j++) { 
  11.                 dp[i] = max(dp[i], dp[i - j] * dp[j]); 
  12.             } 
  13.         } 
  14.         return dp[n]; 
  15.     } 
  16. }; 

這個(gè)代碼也是可以過(guò)的!

在解釋遞推公式的時(shí)候,也可以解釋通,dp[i] 就等于 拆解i - j的最大乘積 * 拆解j的最大乘積??雌饋?lái)沒(méi)毛病!

但是在解釋初始化的時(shí)候,就發(fā)現(xiàn)自相矛盾了,dp[1]為什么一定是1呢?根據(jù)dp[i]的定義,dp[2]也不應(yīng)該是2啊。

但如果遞歸公式是 dp[i] = max(dp[i], dp[i - j] * dp[j]);,就一定要這么初始化。遞推公式?jīng)]毛病,但初始化解釋不通!

雖然代碼在初始位置有一個(gè)判斷if (n <= 3) return 1 * (n - 1);,保證n<=3 結(jié)果是正確的,但代碼后面又要給dp[1]賦值1 和 dp[2] 賦值 2,這其實(shí)就是自相矛盾的代碼,違背了dp[i]的定義!

我舉這個(gè)例子,其實(shí)就說(shuō)做題的嚴(yán)謹(jǐn)性,上面這個(gè)代碼也可以AC,大體上一看好像也沒(méi)有毛病,遞推公式也說(shuō)得過(guò)去,但是僅僅是恰巧過(guò)了而已。

其他語(yǔ)言版本

Java

  1. class Solution { 
  2.     public int integerBreak(int n) { 
  3.         //dp[i]為正整數(shù)i拆分結(jié)果的最大乘積 
  4.         int[] dp = new int[n+1]; 
  5.         dp[2] = 1; 
  6.         for (int i = 3; i <= n; ++i) { 
  7.             for (int j = 1; j < i - 1; ++j) { 
  8.                 //j*(i-j)代表把i拆分為j和i-j兩個(gè)數(shù)相乘 
  9.                 //j*dp[i-j]代表把i拆分成j和繼續(xù)把(i-j)這個(gè)數(shù)拆分,取(i-j)拆分結(jié)果中的最大乘積與j相乘 
  10.                 dp[i] = Math.max(dp[i], Math.max(j * (i - j), j * dp[i - j])); 
  11.             } 
  12.         } 
  13.         return dp[n]; 
  14.     } 

Python

  1. class Solution: 
  2.     def integerBreak(self, n: int) -> int
  3.         dp = [0] * (n + 1) 
  4.         dp[2] = 1 
  5.         for i in range(3, n + 1): 
  6.             # 假設(shè)對(duì)正整數(shù) i 拆分出的第一個(gè)正整數(shù)是 j(1 <= j < i),則有以下兩種方案: 
  7.             # 1) 將 i 拆分成 j 和 i−j 的和,且 i−j 不再拆分成多個(gè)正整數(shù),此時(shí)的乘積是 j * (i-j) 
  8.             # 2) 將 i 拆分成 j 和 i−j 的和,且 i−j 繼續(xù)拆分成多個(gè)正整數(shù),此時(shí)的乘積是 j * dp[i-j] 
  9.             for j in range(1, i - 1): 
  10.                 dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j])) 
  11.         return dp[n] 

Go

  1. func integerBreak(n intint { 
  2.     /** 
  3.     動(dòng)態(tài)五部曲 
  4.     1.確定dp下標(biāo)及其含義 
  5.     2.確定遞推公式 
  6.     3.確定dp初始化 
  7.     4.確定遍歷順序 
  8.     5.打印dp 
  9.     **/ 
  10.     dp:=make([]int,n+1) 
  11.     dp[1]=1 
  12.     dp[2]=1 
  13.     for i:=3;i<n+1;i++{ 
  14.         for j:=1;j<i-1;j++{ 
  15. // i可以差分為i-j和j。由于需要最大值,故需要通過(guò)j遍歷所有存在的值,取其中最大的值作為當(dāng)前i的最大值,在求最大值的時(shí)候,一個(gè)是j與i-j相乘,一個(gè)是j與dp[i-j]. 
  16.             dp[i]=max(dp[i],max(j*(i-j),j*dp[i-j])) 
  17.         } 
  18.     } 
  19.     return dp[n] 
  20. func max(a,b intint
  21.     if a>b{ 
  22.         return a 
  23.     } 
  24.     return b 

Javascript

  1. var integerBreak = function(n) { 
  2.     let dp = new Array(n + 1).fill(0) 
  3.     dp[2] = 1 
  4.  
  5.     for(let i = 3; i <= n; i++) { 
  6.         for(let j = 1; j < i; j++) { 
  7.             dp[i] = Math.max(dp[i], dp[i - j] * j, (i - j) * j) 
  8.         } 
  9.     } 
  10.     return dp[n] 
  11. }; 

 

責(zé)任編輯:姜華 來(lái)源: 代碼隨想錄
相關(guān)推薦

2022-01-04 11:31:15

不同路徑DP

2021-12-28 07:20:44

斐波那契數(shù)算法數(shù)字

2021-05-07 08:02:53

Sentinel 流量服務(wù)

2021-01-13 08:41:08

整數(shù)動(dòng)態(tài)規(guī)劃

2022-01-11 10:01:25

二叉搜索樹(shù)數(shù)量

2023-06-05 12:59:03

2021-12-29 11:32:38

數(shù)據(jù)結(jié)構(gòu)算法爬樓梯

2021-09-01 22:58:22

Canvas標(biāo)簽

2021-07-11 12:12:49

.NETJWTjson

2022-12-28 08:16:16

metric聚合java

2020-05-27 08:05:33

MybatisMapper接口

2021-06-08 09:28:12

.Net通知服務(wù)

2016-11-28 08:40:17

系統(tǒng)降級(jí)服務(wù)

2016-11-25 00:45:37

隊(duì)列數(shù)據(jù)

2016-11-28 09:00:10

瀏覽器瀏覽器緩存服務(wù)端

2021-01-18 10:33:53

Java反射模塊

2022-03-01 17:16:16

數(shù)倉(cāng)建模ID Mapping

2021-12-14 09:01:01

LeetCode整數(shù)羅馬數(shù)字

2021-12-15 09:00:53

LeetCode 羅馬數(shù)字整數(shù)

2025-03-03 10:51:29

SQL數(shù)據(jù)庫(kù)MySQL
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)