343. 整数拆分
动态规划的使用
1 . dp[ i] i; 2. 公式; 3 . dp[ ] 初始化 ; 4. 遍历顺序; 5. 举例说明 ;
dp[ i] = i分裂成至少两个数之和的乘积最大值。
2 = 1 + 1
3 = 1 + 1 + 1 ; - - 3 个数之和
1 + 2 ; 2 个数之和
2 + 1 ; 2 个数之和
dp[ 0 ] = 1 ; dp[ 1 ] = 1
dp[ 2 ] = 1
dp[ 3 ] = dp[ 2 ] * 1 ; - - - > 3 数之和
3
dp[ 2 ] * 1 3 数之和
2 * 1 2 数之和
i= 3
j = 2 , i- 1 = 2
只有 1 * 1 * 1 ; 2 * 1
4
1 * 1 * 1 * 1 - - 3 数之和 + 1 数 = 4 数之和 - - - dp[ 3 ] * 1 四数之和
1 * 1 * 2 dp[ 2 ] * 2 - - - 3 数之和
2 * 2 - - - 2 个数之和 2 * 2
3 + 1 - - - 2 个数之和 3 * 1
i = 4
j = 1 , 2 , 3 ,
dp[ 1 ] * 3 ,
dp[ i- j] , i- j 的最大值
j 2 , 3
dp[ 2 ] * 2 - - 3 数之和
dp[ 3 ] * 1 - - 4 数之和
两个数之和
2 * 2
3 * 1
i : 数据的长度
j : i的数之和的分割遍历
1 <= j < i
j = 2 , dp[ i] = max ( dp[ j] * ( i- j) , j * ( i- j) )
j = 3 , = dp[ i] = max ( dp[ 3 ] * 1 , 3 * 1 , dp[ i] )
dp[ i] = max ( dp[ i] , dp[ j] * ( i- j) , j * ( i- j) )
初始化
dp[ 0 ] = 0
dp[ 1 ] = 1
dp[ 2 ] = 1
for i in range ( 2 , n + 1 ) :
for j in ( 2 , i) :
dp[ i] = max ( dp[ i] , dp[ j] * ( i- j) , j * ( i- j) )
5
1 1 1 1 1 dp[ 4 ] * 1 4 * 1
1 1 1 2 dp[ 3 ] * 2 3 * 2
1 1 3 dp[ 2 ] * 3 2 * 3
1 4
2 3
3 * 2
5
1 1 1 1 1 1
1 1 1 1 2
1 1 1 3
1 1 4
2 2 1 1
2 2 2
2 3 1
2 4
3 * 3
4 * 2
5 * 1
dp[ 6 ] = max ( dp[ 2 ] * 4 , 2 * 4 , dp[ 6 ] )
dp[ 6 ] = max ( dp[ 3 ] * 3 , 3 * 3 , dp[ 6 ] )
dp[ 6 ] = max ( dp[ 4 ] * 2 , 4 * 2 , dp[ 6 ] )
dp[ 6 ] = max ( dp[ 5 ] * 1 , 5 * 1 , dp[ 6 ] )
for i in range ( 2 , n + 1 ) :
for j in ( 1 , i // 2 + 1 ) :
dp[ i] = max ( dp[ i] , dp[ j] * ( i- j) , j * ( i- j) )
from 代码随想录的学习
动态规划
动规五部曲,分析如下:
确定dp数组(dp table)以及下标的含义
dp[ i] :分拆数字i,可以得到的最大乘积为dp[ i] 。
dp[ i] 的定义将贯彻整个解题过程,下面哪一步想不懂了,就想想dp[ i] 究竟表示的是啥!
确定递推公式
可以想 dp[ i] 最大乘积是怎么得到的呢?
其实可以从1 遍历j,然后有两种渠道得到dp[ i] .
一个是j * ( i - j) 直接相乘。
一个是j * dp[ i - j] ,相当于是拆分( i - j) ,对这个拆分不理解的话,可以回想dp数组的定义。
那有同学问了,j怎么就不拆分呢?
j是从1 开始遍历,拆分j的情况,在遍历j的过程中其实都计算过了。那么从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) 是单纯的把整数拆分为两个数相乘,而j * dp[ i - j] 是拆分成两个以及两个以上的个数相乘。
如果定义dp[ i - j] * dp[ j] 也是默认将一个数强制拆成4 份以及4 份以上了。
所以递推公式:dp[ i] = max ( { dp[ i] , ( i - j) * j, dp[ i - j] * j} ) ;
那么在取最大值的时候,为什么还要比较dp[ i] 呢?
因为在递推公式推导的过程中,每次计算dp[ i] ,取最大的而已。
dp的初始化
不少同学应该疑惑,dp[ 0 ] dp[ 1 ] 应该初始化多少呢?
有的题解里会给出dp[ 0 ] = 1 ,dp[ 1 ] = 1 的初始化,但解释比较牵强,主要还是因为这么初始化可以把题目过了。
严格从dp[ i] 的定义来说,dp[ 0 ] dp[ 1 ] 就不应该初始化,也就是没有意义的数值。
拆分0 和拆分1 的最大乘积是多少?
这是无解的。
这里我只初始化dp[ 2 ] = 1 ,从dp[ i] 的定义来说,拆分数字2 ,得到的最大乘积是1 ,这个没有任何异议!
确定遍历顺序
确定遍历顺序,先来看看递归公式:dp[ i] = max ( dp[ i] , max ( ( i - j) * j, dp[ i - j] * j) ) ;
dp[ i] 是依靠 dp[ i - j] 的状态,所以遍历i一定是从前向后遍历,先有dp[ i - j] 再有dp[ i] 。
所以遍历顺序为:
for i in range(3, n + 1):
for j in range(1, i):
dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j))
注意 枚举j的时候,是从1开始的。从0开始的话,那么让拆分一个数拆个0,求最大乘积就没有意义了。
j的结束条件是 j < i - 1 ,其实 j < i 也是可以的,不过可以节省一步,例如让j = i - 1,的话,其实在 j = 1的时候,这一步就已经拆出来了,重复计算,所以 j < i - 1
至于 i是从3开始,这样dp[i - j]就是dp[2]正好可以通过我们初始化的数值求出来。
更优化一步,可以这样:
for i in range(3, n + 1):
for j in range(1, i//2 + 1):
dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j))
因为拆分一个数n 使之乘积最大,那么一定是拆分成m个近似相同的子数相乘才是最大的。
例如 6 拆成 3 * 3, 10 拆成 3 * 3 * 4。 100的话 也是拆成m个近似数组的子数 相乘才是最大的。
只不过我们不知道m究竟是多少而已,但可以明确的是m一定大于等于2,既然m大于等于2,也就是 最差也应该是拆成两个相同的 可能是最大值。
那么 j 遍历,只需要遍历到 n/2 就可以,后面就没有必要遍历了,一定不是最大值。
343.整数拆分
本题也可以用贪心,每次拆成n个3 ,如果剩下是4 ,则保留4 ,然后相乘,但是这个结论需要数学证明其合理性!
class Solution :
def integerBreak ( n) :
if ( n == 2 ) return 1 ;
if ( n == 3 ) return 2 ;
if ( n == 4 ) return 4 ;
result = 1
while ( n > 4 ) :
result *= 3
n -= 3
result *= n
return result
96.不同的二叉搜索树
给定一个整数 n,求以 1 . . . n 为节点组成的二叉搜索树有多少种?
3 : 5 种
二叉搜索树 左 < 中 < 右
dp[ 0 ] = 1
1 : 1
2 : 1 为根,2 在右; 2 为根, 1 在左; dp[ 0 ] * dp[ 1 ] + dp[ 1 ] * dp[ 0 ] = 2
3 : 1 为根, 2 、3 在右- - 大小关系 2 3 ; 3 2 dp[ 2 ] ; 2 在根、1 左、3 右; 3 在根, 1 、2 左 dp[ 2 ] , 右dp[ 0 ] ; = dp[ 0 ] * dp[ 2 ] + dp[ 1 ] * dp[ 1 ] + dp[ 2 ] * dp[ 0 ] = 2 + 1 + 2 = 5
dp[ i] : 整 i 为节点组成的二叉搜索树有 dp[ i] 种.
dp[ i] = [ dp[ j] * dp[ i - j - 1 ] for i in range ( 0 , i) ]
dp[ i] = sum ( dp[ i] )
3. 初始化: dp[ 0 ] = 1 , dp[ 1 ] = 1 dp[ 2 ] = 2
4. 遍历顺序: 前往后 i - - 0 , 1 , 2 , 3 , 4 , . . . . , i- 1.
5. 举例子,如上。
dp = [ 0 ] * ( n + 1 )
dp[ 0 ] = 1
dp[ 1 ] = 1
dp[ 2 ] = 2
for i in range ( 3 , n + 1 ) :
for j in range ( 0 , i) :
dp[ i] += dp[ j] * dp[ i- j- 1 ]
return dp[ n]