leetcode
leetcode 2151 ~ 2200
好分区的数目

好分区的数目

难度:

标签:

题目描述

You are given an array nums consisting of positive integers and an integer k.

Partition the array into two ordered groups such that each element is in exactly one group. A partition is called great if the sum of elements of each group is greater than or equal to k.

Return the number of distinct great partitions. Since the answer may be too large, return it modulo 109 + 7.

Two partitions are considered distinct if some element nums[i] is in different groups in the two partitions.

 

Example 1:

Input: nums = [1,2,3,4], k = 4
Output: 6
Explanation: The great partitions are: ([1,2,3], [4]), ([1,3], [2,4]), ([1,4], [2,3]), ([2,3], [1,4]), ([2,4], [1,3]) and ([4], [1,2,3]).

Example 2:

Input: nums = [3,3,3], k = 4
Output: 0
Explanation: There are no great partitions for this array.

Example 3:

Input: nums = [6,6], k = 2
Output: 2
Explanation: We can either put nums[0] in the first partition or in the second partition.
The great partitions will be ([6], [6]) and ([6], [6]).

 

Constraints:

  • 1 <= nums.length, k <= 1000
  • 1 <= nums[i] <= 109

代码结果

运行时间: 83 ms, 内存: 16.2 MB


import java.util.*;
import java.util.stream.*;

/**
 * 使用 Java Stream API 实现的题解。
 * 给定一个正整数数组 nums 和一个整数 k。
 * 分区的定义是:将数组划分成两个有序的组,并满足每个元素恰好存在于某一个组中。
 * 如果分区中每个组的元素和都大于等于 k,则认为分区是一个好分区。
 * 返回不同的好分区的数目。由于答案可能很大,请返回对 10^9 + 7 取余后的结果。
 */
public class GoodPartitionsStream {
    private static final int MOD = 1_000_000_007;

    public int countGoodPartitions(int[] nums, int k) {
        int n = nums.length;
        return (int) IntStream.range(0, (1 << n))
                .filter(mask -> {
                    long sum1 = IntStream.range(0, n)
                            .filter(i -> (mask & (1 << i)) != 0)
                            .mapToLong(i -> nums[i])
                            .sum();
                    long sum2 = IntStream.range(0, n)
                            .filter(i -> (mask & (1 << i)) == 0)
                            .mapToLong(i -> nums[i])
                            .sum();
                    return sum1 >= k && sum2 >= k;
                })
                .count() % MOD;
    }

    public static void main(String[] args) {
        GoodPartitionsStream solution = new GoodPartitionsStream();
        int[] nums1 = {1, 2, 3, 4};
        int k1 = 4;
        System.out.println(solution.countGoodPartitions(nums1, k1)); // 输出:6

        int[] nums2 = {3, 3, 3};
        int k2 = 4;
        System.out.println(solution.countGoodPartitions(nums2, k2)); // 输出:0

        int[] nums3 = {6, 6};
        int k3 = 2;
        System.out.println(solution.countGoodPartitions(nums3, k3)); // 输出:2
    }
}

解释

方法:

这道题解的核心是使用动态规划来确定可以形成好分区的组合数。首先,如果数组总和小于2*k,则不可能形成两个组,每个组的和都至少为k。因此,如果数组和小于2*k,直接返回0。使用一个动态规划数组f,其中f[j]表示通过选择部分元素能够形成和为j的方式数。初始化f[0]=1,表示和为0的方式有一种(即一个元素都不选)。遍历数组中的每个元素x,从k-1向下至x更新f数组,f[j]更新为f[j]和f[j-x]的和,即考虑不选x和选x两种情况。最后,计算所有可能的分区方式,即2的nums长度次方,减去那些不符合好分区的情况(即两个分区中至少有一个的和小于k的情况,这些情况的总数是f数组中从1到k-1的元素的两倍),然后对结果取模。

时间复杂度:

O(n*k)

空间复杂度:

O(k)

代码细节讲解

🦆
在动态规划的过程中,为什么要从k-1向下至x更新数组f?为什么不能从x向上到k-1更新?
在动态规划中从k-1向下至x更新数组f是为了避免元素x在同一次遍历中被重复使用。如果从x向上到k-1更新,当前元素x的加入可能会立即影响到基于它自身之前的值计算出的新值,这会导致某些数值被重复计算多次,违背了每个元素仅用一次的原则。从高到低的更新确保当处理到f[j]时,f[j-x]仍然是没有加上当前元素x之前的状态,从而保持了状态的正确转移。
🦆
动态规划数组f初始化时,f[0]为什么设为1而其他索引的值为0?
动态规划数组f中的f[0]=1表示和为0的方式有一种,即不选择任何元素的情况。这是基本的边界条件,作为动态规划解决子问题的基础。对于其他索引的值初始化为0,是因为在开始时,没有任何元素被选择,因此除了和为0之外,形成其他和的方式数都应该是0,直到遍历到具体的元素并更新这些值。
🦆
如何确保遍历数组元素并更新动态规划数组时,不会出现重复计算同一个元素的情况?
通过从高到低更新动态规划数组(即从k-1到x),确保在一次元素遍历中,每个元素只被计算一次。这种更新方法意味着在更新f[j]时,依赖的f[j-x]是基于当前元素之前的状态,还未被当前元素影响。这样可以保证每个元素在计算各个状态值时只被使用一次,避免重复计算。
🦆
题解中提到的计算不符合好分区的情况,具体是如何计算从1到k-1的元素的两倍的?
在题解中,不符合好分区的情况是指两个分区中至少有一个的和小于k。动态规划数组f[j]记录的是形成和为j的分区方法数。因此,计算所有不符合条件的分区方案数,就是计算f[1]到f[k-1]的总和,并将此和乘以2,因为每个这样的和都可以出现在两个分区中的任何一个。最后,从总的分区方法数中减去这个乘以2后的数,得到符合条件的分区方法数。

相关问题