动态规划-整数拆分(集合角度推导)

题目

整数拆分
给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。

示例 1:

输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1。
示例 2:

输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。
说明: 你可以假设 n 不小于 2 且不大于 58。

1. 集合定义

对于一个整数 ( n ),将其拆分的所有方式记为集合 ( S(n) ):
S ( n ) = { ( a 1 , a 2 , … , a k ) ∣ a 1 + a 2 + ⋯ + a k = n ,   a i > 0 ,   k ≥ 2 } S(n) = \{(a_1, a_2, \dots, a_k) \mid a_1 + a_2 + \cdots + a_k = n, \, a_i > 0, \, k \geq 2\} S(n)={(a1,a2,,ak)a1+a2++ak=n,ai>0,k2}

每种拆分方式对应一个乘积:
P ( a 1 , a 2 , … , a k ) = a 1 × a 2 × ⋯ × a k P(a_1, a_2, \dots, a_k) = a_1 \times a_2 \times \cdots \times a_k P(a1,a2,,ak)=a1×a2××ak

我们需要在这些拆分方式中找到乘积最大的那种,记为 d p [ n ] dp[n] dp[n]
d p [ n ] = max ⁡ ( a 1 , a 2 , … , a k ) ∈ S ( n ) P ( a 1 , a 2 , … , a k ) dp[n] = \max_{(a_1, a_2, \dots, a_k) \in S(n)} P(a_1, a_2, \dots, a_k) dp[n]=(a1,a2,,ak)S(n)maxP(a1,a2,,ak)


2. 集合拆解

考虑将整数 n n n 拆分为两部分 j j j n − j n-j nj,其中 j j j是拆分点:
n = j + ( n − j ) n = j + (n-j) n=j+(nj)

此时可以将 S ( n ) S(n) S(n)拆解为两类子集合:

  1. 直接拆分:只将 n n n 拆分为两部分 j j j n − j n-j nj,对应的乘积为:
    P ( j , n − j ) = j × ( n − j ) P(j, n-j) = j \times (n-j) P(j,nj)=j×(nj)

  2. 递归拆分:进一步对 n − j n-j nj继续拆分,对应的乘积为:
    P ( j , S ( n − j ) ) = j × d p [ n − j ] P(j, S(n-j)) = j \times dp[n-j] P(j,S(nj))=j×dp[nj]

因此,集合 S ( n ) S(n) S(n)的最大值可以通过以下公式表示:
d p [ n ] = max ⁡ 1 ≤ j < n { j × ( n − j ) , j × d p [ n − j ] } dp[n] = \max_{1 \leq j < n} \{j \times (n-j), j \times dp[n-j]\} dp[n]=1j<nmax{j×(nj),j×dp[nj]}


3. 动态规划状态转移公式

通过上述分析,可以总结出状态转移公式:
d p [ n ] = max ⁡ 1 ≤ j < n { max ⁡ ( j × ( n − j ) , j × d p [ n − j ] ) } dp[n] = \max_{1 \leq j < n} \{\max(j \times (n-j), j \times dp[n-j])\} dp[n]=1j<nmax{max(j×(nj),j×dp[nj])}

其中:

  • j × ( n − j ) j \times (n-j) j×(nj) 表示不递归拆分,直接将 n n n 拆为 j j j n − j n-j nj
  • j × d p [ n − j ] j \times dp[n-j] j×dp[nj] 表示递归使用之前的最优解。

4. 初始化条件

动态规划需要初始值:

  • d p [ 1 ] = 1 dp[1] = 1 dp[1]=1(虽然通常不拆分,但作为递归的基准)。
  • d p [ 2 ] = 1 dp[2] = 1 dp[2]=1(2 拆分为 1 和 1,乘积为 1)。

5. 集合视角的递推步骤

从小到大递推:

  1. 对于每个 n n n,枚举所有可能的拆分点 j j j
  2. 计算拆分后的两个子问题:
    • 不再拆分的乘积: j × ( n − j ) j \times (n-j) j×(nj)
    • 继续递归的乘积: j × d p [ n − j ] j \times dp[n-j] j×dp[nj]
  3. 在所有拆分中找到最大值,赋值给 d p [ n ] dp[n] dp[n]

示例推导 $n = 5 )

集合拆分视角分析:

  • S ( 5 ) S(5) S(5):所有可能的拆分:
    S ( 5 ) = { ( 4 , 1 ) , ( 3 , 2 ) , ( 3 , 1 , 1 ) , ( 2 , 2 , 1 ) , ( 2 , 1 , 1 , 1 ) , ( 1 , 1 , 1 , 1 , 1 ) } S(5) = \{(4, 1), (3, 2), (3, 1, 1), (2, 2, 1), (2, 1, 1, 1), (1, 1, 1, 1, 1)\} S(5)={(4,1),(3,2),(3,1,1),(2,2,1),(2,1,1,1),(1,1,1,1,1)}
  • 最大乘积来自以下两种情况:
    1. 直接拆分为两部分,枚举 j = 1 , 2 , 3 , 4 j = 1, 2, 3, 4 j=1,2,3,4
    2. 递归拆分,利用 d p [ n − j ] dp[n-j] dp[nj]

动态规划计算:

  1. 初始化: d p [ 1 ] = 1 dp[1] = 1 dp[1]=1 d p [ 2 ] = 1 dp[2] = 1 dp[2]=1
  2. 递推:
    • d p [ 3 ] = max ⁡ ( 1 × 2 , 1 × d p [ 2 ] ) = 2 dp[3] = \max(1 \times 2, 1 \times dp[2]) = 2 dp[3]=max(1×2,1×dp[2])=2
    • d p [ 4 ] = max ⁡ ( 1 × 3 , 1 × d p [ 3 ] , 2 × 2 ) = 4 dp[4] = \max(1 \times 3, 1 \times dp[3], 2 \times 2) = 4 dp[4]=max(1×3,1×dp[3],2×2)=4
    • d p [ 5 ] = max ⁡ ( 1 × 4 , 1 × d p [ 4 ] , 2 × 3 , 2 × d p [ 3 ] ) = 6 dp[5] = \max(1 \times 4, 1 \times dp[4], 2 \times 3, 2 \times dp[3]) = 6 dp[5]=max(1×4,1×dp[4],2×3,2×dp[3])=6

最终答案: d p [ 5 ] = 6 dp[5] = 6 dp[5]=6

1. 优化状态转移

当前的状态转移公式的时间复杂度为 O ( n 2 ) O(n^2) O(n2),因为每次计算 d p [ n ] dp[n] dp[n] 都需要枚举所有可能的拆分点 j j j。可以考虑利用数学性质来减少不必要的计算。

优化思路:

  • 减小计算量:拆分 n n n j j j n − j n-j nj 时,最大乘积通常出现在 j j j n − j n-j nj较为均匀的情况。因此,可以优先考虑接近 n 2 \frac{n}{2} 2n 的拆分点。

  • 剪枝:在实际实现中,可以通过观察某些 ( j ) 的拆分结果始终小于其他拆分的结果,从而剪去这些不必要的拆分点,加速计算。

2. 递归与迭代

动态规划可以通过递归或迭代来实现。递归形式易于理解,但迭代实现通常更高效,避免了函数调用的开销。

迭代实现的伪代码如下:

def integerBreak(n):
    dp = [0] * (n + 1)
    dp[1] = 1
    dp[2] = 1
    for i in range(3, n + 1):
        for j in range(1, i):
            dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j]))
    return dp[n]

补充

公式 j × ( n − j ) j \times (n-j) j×(nj) 的由来,源于将整数 n n n 拆分成两部分时的直接乘积计算。它的推导过程如下:


1. 拆分的基本思想

整数拆分问题的核心是将一个整数 n n n 分成若干个整数的和:
n = a 1 + a 2 + ⋯ + a k n = a_1 + a_2 + \dots + a_k n=a1+a2++ak
为了最大化这些整数的乘积 P ( a 1 , a 2 , … , a k ) P(a_1, a_2, \dots, a_k) P(a1,a2,,ak),我们需要枚举可能的拆分方式。

一种最简单的拆分方式是只把 n n n 拆成两个整数 j j j n − j n-j nj,其中 j j j 是一个拆分点。此时,乘积为:
j × ( n − j ) j \times (n-j) j×(nj)

这直接反映了拆分点 j j j 的贡献和剩余部分 n − j n-j nj 的乘积。


2. 从数学推导来看

对于一个整数 n n n,如果只考虑拆分为两部分 j j j n − j n-j nj,它的乘积公式自然就是:
P = j × ( n − j ) P = j \times (n-j) P=j×(nj)
这里:

  • j j j 是第一个拆分出的部分。
  • n − j n-j nj 是剩下的部分。

这个公式来源于简单的代数计算:如果将 n n n 拆分为两部分,二者的乘积就是这些部分相乘。


3. 从动态规划的角度来看

动态规划的问题需要从小到大逐步计算。对于 d p [ n ] dp[n] dp[n],我们尝试枚举拆分点 j j j,将 n n n 分为两部分:

  1. 一部分是 j j j,它不会再拆分。
  2. 另一部分是 n − j n-j nj,它可以继续拆分。

直接拆分的乘积公式就是:
j × ( n − j ) j \times (n-j) j×(nj)
如果只考虑这一步拆分,这是计算的直接结果。


4. 为何需要直接乘积 j × ( n − j ) j \times (n-j) j×(nj)

在动态规划中,最大乘积有两种情况:

  1. 直接拆分的结果
    • 拆分为 j j j n − j n-j nj,只计算这两部分的乘积:
      P = j × ( n − j ) P = j \times (n-j) P=j×(nj)
    • 这是不进一步递归时的结果。
  2. 递归子问题的结果
    • 如果 n − j n-j nj 可以进一步拆分,我们取其最大乘积 d p [ n − j ] dp[n-j] dp[nj],然后乘以 j j j
      P = j × d p [ n − j ] P = j \times dp[n-j] P=j×dp[nj]
      两者都需要考虑,取其最大值。

5. 为什么需要枚举 j j j

在计算最大乘积时,拆分点 j j j 的选择对结果影响很大。例如:

  • j j j 较小时,剩余部分 n − j n-j nj 较大,可能需要进一步拆分。
  • j j j 较大时,剩余部分较小,可能直接求最大乘积。

通过枚举 j j j,我们可以覆盖所有可能的拆分情况,确保找到最大值。


6. 直观理解 j × ( n − j ) j \times (n-j) j×(nj) 的作用

  • j × ( n − j ) j \times (n-j) j×(nj)拆分后的直接乘积,体现了最简单的拆分方式。
  • 这是一种边界条件(base case),即当剩余部分 n − j n-j nj 不再进一步拆分时的结果。
  • 它提供了动态规划的基础值,与 j × d p [ n − j ] j \times dp[n-j] j×dp[nj] 共同构成状态转移的核心逻辑。

总结

公式 j × ( n − j ) j \times (n-j) j×(nj) 的灵感来自于拆分的基本操作,即将整数 n n n 分为两部分的直接乘积:

  1. 是最简单的整数拆分形式。
  2. 作为动态规划的一种情况,帮助递归计算中找到最大乘积。
  3. 构成状态转移公式的一部分,与递归解法( j × d p [ n − j ] j \times dp[n-j] j×dp[nj])相辅相成,确保覆盖所有可能的拆分情况。
### 整数因子分解问题解析 对于整数因子分解问题,目标是对给定的正整数 \( n \) 计算其所有可能的不同分解方式的数量。以下是基于引用内容和专业知识的回答。 #### 方法一:枚举法 通过枚举的方式可以解决此问题。具体来说,可以通过递归方法来遍历所有的因数组合[^1]。 设函数 `count_decompositions(n)` 表示计算 \( n \) 的不同分解数量,则可以用如下逻辑实现: ```python def count_decompositions(n, start=2): # 如果当前数值为1,表示已经完成一种分解 if n == 1: return 1 total = 0 # 枚举从start到sqrt(n)的所有可能因子 for i in range(start, int(n ** 0.5) + 1): if n % i == 0: # 判断i是否为n的因子 total += count_decompositions(n // i, i) # 考虑单独作为一项的情况 if n >= start: total += 1 return total # 测试样例 print(count_decompositions(12)) # 输出应为8 ``` 上述代码实现了对整数 \( n \) 所有可能分解形式的计数功能。其中,`start` 参数用于控制递归过程中因子的选择范围,从而避免重复统计相同的分解组合。 --- #### 方法二:质因子分解与优化 另一种思路是从质因子的角度出发解决问题。通过对输入 \( n \) 进行逐步除法操作,提取出所有质因子并记录它们的幂次[^2]。虽然这种方法主要用于展示质因子结构,但它也可以间接帮助理解如何构建完整的分解表达式。 以下是一个简单例子,演示如何获取某个数的质因子列表及其对应的指数: ```python from collections import defaultdict def prime_factorization(n): factors = defaultdict(int) div = 2 while div * div <= n: while n % div == 0: factors[div] += 1 n //= div div += 1 if n > 1: factors[n] += 1 return dict(factors) # 测试样例 result = prime_factorization(12) print(result) # 输出 {2: 2, 3: 1} ``` 尽管这段代码并未直接提供分解总数,但它是进一步推导的基础工具之一。 --- #### 方法三:连续自然数求和分解 除了传统的乘积型分解外,还可以尝试将一个正整数表示为若干个连续自然数之和的形式[^3]。这种转换不仅增加了趣味性,还引入了新的数学视角。 例如,已知某数能够被拆分为多个连续项相加之总和时,可利用滑动窗口技术动态调整区间边界直至满足条件为止[^4]。下面给出相应实现方案: ```python def find_consecutive_sums(N): result = [] i, j = 1, 2 current_sum = sum(range(i, j)) while True: if current_sum == N: result.append(list(range(i, j))) if current_sum >= N or j - i <= 1: current_sum -= i i += 1 else: j += 1 current_sum += j if i >= j or current_sum < N and j > N: break return result # 测试样例 output = find_consecutive_sums(15) for seq in output: print(seq) # 可能输出 [[7, 8], [4, 5, 6], [1, 2, 3, 4, 5]] ``` 以上程序展示了如何找到所有符合条件的连续子序列集合][^[^34]。 --- ### 总结 综上所述,针对整数因子分解这一主题,存在多种解题策略可供选择。无论是采用暴力枚举还是借助质因子特性简化运算过程,亦或是探索其他变形模式如连续自然数加法规律,均体现了算法设计中的灵活性与创造性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值