大分区的数量

2025年2月7日 | 阅读 12 分钟

问题陈述

在此问题陈述中,我们给定一个包含正整数的数组 nums 和一个整数 k。将数组划分为两个有序组,使得每个元素恰好属于一个组。如果每组元素的和都大于或等于 k,则称该划分是“great”的。返回不同“great”划分的数量。由于答案可能非常大,请对 10^9 + 7 取模返回。如果两个划分中某个元素 nums[i] 所在的组不同,则这两个划分被视为不同。

示例 1

输入: nums = [1,2,3,4], k = 4

输出 6

说明

划分 1:组 1 = [1, 2, 3],组 2 = [4]。 (两组元素的和 = 6 > 4)

划分 2:组 1 = [1, 3],组 2 = [2, 4]。 (两组元素的和 = 4 = 4)

划分 3:组 1 = [1, 4],组 2 = [2, 3]。 (两组元素的和 = 5 > 4)

划分 4:组 1 = [2, 3],组 2 = [1, 4]。 (两组元素的和 = 5 > 4)

划分 5:组 1 = [2, 4],组 2 = [1, 3]。 (两组元素的和 = 6 > 4)

划分 6:组 1 = [4],组 2 = [1, 2, 3]。 (两组元素的和 = 7 > 4)

共有 6 种划分,其中每组元素的和都大于或等于 4。因此,此示例的输出是 6。

示例 2

输入: nums = [3,3,3], k = 4

输出 0

说明

划分 1:组 1 = [3, 3],组 2 = [3]。 (两组元素的和 = 6 > 4)

划分 2:组 1 = [3],组 2 = [3, 3]。 (两组元素的和 = 6 > 4)

这些划分都满足每组元素总和大于或等于 4 的要求。但由于它们包含相同的组件,因此划分不是唯一的。因此,在此情况下,输出为 0。

使用背包问题的 Java 方法

输出

Number of Great Partitions
Number of Great Partitions

代码解释

初始化

  • 指定 10^9 + 7 作为模值初始化。
  • 初始化 total 以存储数组中元素的总和。
  • 将 res 初始化为 1 以保存结果。
  • 建立一个大小为 k 的 DP 数组 dp,用于存储达到每个和的方法数。

动态规划

  • 遍历数组 A 中的每个元素 a。
  • 对于每个元素 a,使用动态规划更新 DP 数组,以计算从 0 到 k - 1 的每个和的方法数。
  • 通过从 k - 1 - a 到 0 向后更新 DP 数组的值来实现。对于每个索引 i,通过将 dp[i] 添加到 dp[i + a] 来更新 dp[i + a]。
  • 确保所有计算都按模 mod 执行。

更新结果

  • 在为每个元素实现 DP 数组后,将 res 变量乘以 2,然后取模 mod。
  • 通过对每个元素求和来获取数组中元素的总和。

最终结果计算

  • 逐一遍历 DP 数组的索引 i。
  • 根据 DP 数组的值和总和调整 res。如果 total - i 小于 k,则从 res 中减去 dp[i]。否则,减去 dp[i] * 2。

返回结果

  • 取模 mod 后返回最终结果。

时间复杂度

  • 时间复杂度为 O(nk) 的算法将实现,其中 n 和 k 分别是输入数组 A 的长度和划分数。因此,当有超过 O(A) 个元素时,由于其中存在 O(n) 和 O(k) 次迭代的嵌套循环,以及一个扫描 A 的第 i 个元素的 dp 数组,时间复杂度为 O(n*k)。

空间复杂度

  • 空间复杂度为 O(k),因为算法使用大小为 k 的动态数组作为处理空间来存储每个可能和的划分数。此外,还存在一些常规的空间变量。

使用递归+记忆化的 Java 方法

输出

Number of Great Partitions
Number of Great Partitions

代码解释

初始化:

  • 为常量 mod 指定模运算。
  • 变量 sum 用于存储数组 nums 中所有元素的总和。
  • 由于如果 total (sum) 小于 2 倍的划分数 (2 * k),则不可能生成具有非零和的 k 个划分,因此相关函数返回 0。

记忆化数组

  • 初始化大小为 (n + 1) x (k + 1) 的数组 memory 来存储每个子问题的结果。
  • 所有数组元素都初始化为 -1,每个元素都表示该子问题的结果尚未计算。

递归函数 countPar

  • 此特定函数以递归方式计算划分数。
  • 它同时考虑两种情况:notPick(排除当前数字在划分中)和 selecting(包含它)。
  • 如果当前数字等于剩余和(k > nums[index - 1]),则以减小的和递归执行 countPar。

主函数 countPartitions

  • 算法从这个函数开始。
  • 它使用递归函数 countPar 来获得划分的总数。
  • 接下来,它使用公式 (Math.pow(2, n) % mod) 来确定可以生成的划分总数,即 2^n。
  • 最后,它返回“非 great”划分的总数与所有可行划分的总数之间的差值,如果需要,则加上 mod 以确保结果为非负。

时间复杂度

  • 算法的时间复杂度可以通过计算记忆空间分配的维度来获得,即 (n+1)*(k+1),其中 n 是数组中的项目数,k 是差值。

空间复杂度

  • 空间复杂度也是 O(n*k),因为记忆化数组中的分配是输入大小,并且需要保留算法计数器。

使用制表的 Java 方法

输出

Number of Great Partitions
Number of Great Partitions

代码解释

初始化

  • 为了执行模运算,算法将 mod 变量的初始值设置为 10^9 + 7。
  • 它计算并存储变量 sum,即数组 nums 中所有元素的总和。

基本情况检查

  • 如果元素总和 (sum) 小于划分数 (2 * k) 的两倍,则不可能创建具有非零和的 k 个划分。在这种情况下,函数返回 0。

动态规划方法

  • 使用大小为 (n + 1) x (k + 1) 的二维数组 dp 来存储子问题的中间结果。
  • dp[i][j] 表示将数组的前 i 个元素划分成恰好 j 个分区的数量。

基本情况初始化

  • 对于每个目标分区 (target),基本情况初始化为 2,表示两种可能性:当前分区为空或包含单个元素。

填充 DP 表

  • 算法遍历数组中的每个元素和每个潜在的目标分区。
  • 它考虑两种情况:a:选择(将当前元素包含在当前分区中)和不选择(notPick)。
  • 基于这些实例,修改 dp[index][target] 的值。

计算总分区数

  • 不符合“great”条件的划分数量(即不满足其和大于 0 的条件)存储在变量 notGreatPartitions 中。
  • 使用公式 (2^n) % mod,变量 total 确定可能的分区总数。
  • 最后,算法返回“非 great”划分的数量与潜在划分总数之间的差值。

时间复杂度

  • 该解法的时间复杂度估计为 O(n * k),其中 n 是输入数组的长度,k 是查询的数量。由于两个 for 循环分别遍历数组元素和所有可能的和,因此上述条件成立。

空间复杂度

  • 空间复杂度也为 O(n*k),这一点也很重要。原因之一可能是评估方法是二维的,并且该数组用于显示每个索引和目标和的组合。此外,还为数组元素的总和等值和其他类似详细信息进行了空间预留。

使用制表法的 Java 方法 - 空间优化

输出

Number of Great Partitions
Number of Great Partitions

代码解释

初始化

  • 为了处理模运算,算法将 mod 变量初始化为 10^9 + 7。它计算并存储变量 sum,即数组 nums 中所有元素的总和。
  • 基本情况验证:算法确定 sum(所有元素总和)是否小于 2 * k。
  • 如果是这样,则无法创建具有非零和的 k 个分区。在这种情况下,函数返回 0。

动态规划方法

  • 该算法使用动态规划来找到解决该问题更有效的方法。
  • 它维护两个数组:curr 和 prev。curr[target] 基于 prev 数组更新,prev[target] 表示使用直到前一个索引的元素形成和为 target 的分区数。

基本情况初始化

  • 对于每个目标分区 (target),基本情况初始化为 2,表示两种可能性:当前分区为空或包含单个元素。

填充 DP 表

  • 算法遍历数组中的每个元素以及每个可能的 target 分区。
  • 在每一步,它考虑两种情况:
    • 包含当前元素 (pick):它还使用 prev 数组计算具有和 target - nums[index - 1] 的分区数。
    • 不包含当前元素 (notPick):它从 prev 数组中检索具有和 target 的分区数。
  • curr[target] 的值通过 pick 和 notPick 的和(取模 mod)进行更新。

计算总分区数

  • 该算法使用公式 (2^n)% mod 来确定总分区数 (total)。

通过获得 prev[k] 的值,它确定了“非 great”分区的数量 (notGreatPartitions)。

该过程最后返回 total 和 notGreatPartitions 之间的差值(取模 mod)。

时间复杂度

  • 上述代码的时间复杂度为 O(n * k),其中 n 是 nums 的长度,k 是给定的 targetSum。由于为了查找所需和而对数组元素进行嵌套循环,因此存在这种复杂度。

空间复杂度

  • 空间复杂度为 O(k),其中 k 是和。首先,我们有一个长度为 k 的额外数组,用于存储每个目标和的划分索引,这比存储大小为 n*k 的整个 dp 数组要节省得多。