Hard
题目描述
给你一个由正整数组成的数组 nums 和一个整数 k。
将数组分割成两个有序组,使得每个元素恰好在一个组中。如果每个组的元素和都大于或等于 k,则称该分割为优秀分割。
返回不同优秀分割的数量。由于答案可能很大,返回对 10^9 + 7 取模的结果。
如果某个元素 nums[i] 在两个分割中属于不同的组,则认为这两个分割是不同的。
示例 1:
输入:nums = [1,2,3,4], k = 4
输出:6
解释:优秀分割有:([1,2,3], [4])、([1,3], [2,4])、([1,4], [2,3])、([2,3], [1,4])、([2,4], [1,3]) 和 ([4], [1,2,3])。
示例 2:
输入:nums = [3,3,3], k = 4
输出:0
解释:对于这个数组,没有优秀分割。
示例 3:
输入:nums = [6,6], k = 2
输出:2
解释:我们可以将 nums[0] 放在第一个分割或第二个分割中。
优秀分割是 ([6], [6]) 和 ([6], [6])。
约束条件:
1 <= nums.length, k <= 10001 <= nums[i] <= 10^9
解题思路
这道题要求我们计算优秀分割的数量,其中优秀分割指两个组的元素和都大于等于k。
核心思路:
我们可以采用反向思维来解决这个问题。总的分割数量是 2^n(每个元素可以选择放入第一组或第二组),我们需要减去不符合条件的分割数量。
不符合条件的分割是指至少有一个组的和小于k的分割。设第一组元素和为sum1,第二组元素和为sum2,总和为total。由于sum1 + sum2 = total,如果sum1 < k,那么sum2 = total - sum1 > total - k。
算法步骤:
- 首先检查total是否小于2*k,如果是则无法形成优秀分割,返回0
- 使用动态规划计算第一组和小于k的分割数量
- 由于对称性,第二组和小于k的分割数量相同
- 需要减去两组和都小于k的重复计算(如果存在的话)
- 最终答案 = 2^n - 2 * (第一组和<k的分割数) + (两组和都<k的分割数)
DP状态定义:
dp[i][j]表示前i个元素中选择一些元素使得和为j的方案数- 状态转移:
dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i-1]]
由于nums[i]可能很大,我们需要优化空间,只考虑和小于k的情况。
代码实现
class Solution {
public:
int countPartitions(vector<int>& nums, int k) {
const int MOD = 1e9 + 7;
int n = nums.size();
long long total = 0;
for (int num : nums) {
total += num;
}
if (total < 2LL * k) return 0;
// dp[j] = number of ways to get sum j
vector<long long> dp(k, 0);
dp[0] = 1;
for (int num : nums) {
for (int j = k - 1; j >= num; j--) {
dp[j] = (dp[j] + dp[j - num]) % MOD;
}
}
// Count partitions where first group sum < k
long long bad = 0;
for (int j = 0; j < k; j++) {
bad = (bad + dp[j]) % MOD;
}
// Total partitions = 2^n
long long total_partitions = 1;
for (int i = 0; i < n; i++) {
total_partitions = (total_partitions * 2) % MOD;
}
// Answer = total - 2 * bad (by symmetry, second group sum < k has same count)
long long ans = (total_partitions - 2 * bad % MOD + MOD) % MOD;
return ans;
}
};
class Solution:
def countPartitions(self, nums: List[int], k: int) -> int:
MOD = 10**9 + 7
n = len(nums)
total = sum(nums)
if total < 2 * k:
return 0
# dp[j] = number of ways to get sum j
dp = [0] * k
dp[0] = 1
for num in nums:
for j in range(k - 1, num - 1, -1):
dp[j] = (dp[j] + dp[j - num]) % MOD
# Count partitions where first group sum < k
bad = sum(dp) % MOD
# Total partitions = 2^n
total_partitions = pow(2, n, MOD)
# Answer = total - 2 * bad (by symmetry)
ans = (total_partitions - 2 * bad) % MOD
return ans
public class Solution {
public int CountPartitions(int[] nums, int k) {
const int MOD = 1000000007;
int n = nums.Length;
long total = 0;
foreach (int num in nums) {
total += num;
}
if (total < 2L * k) return 0;
// dp[j] = number of ways to get sum j
long[] dp = new long[k];
dp[0] = 1;
foreach (int num in nums) {
for (int j = k - 1; j >= num; j--) {
dp[j] = (dp[j] + dp[j - num]) % MOD;
}
}
// Count partitions where first group sum < k
long bad = 0;
for (int j = 0; j < k; j++) {
bad = (bad + dp[j]) % MOD;
}
// Total partitions = 2^n
long totalPartitions = 1;
for (int i = 0; i < n; i++) {
totalPartitions = (totalPartitions * 2) % MOD;
}
// Answer = total - 2 * bad (by symmetry)
long ans = (totalPartitions - 2 * bad % MOD + MOD) % MOD;
return (int)ans;
}
}
var countPartitions = function(nums, k) {
const MOD = 1e9 + 7;
const n = nums.length;
const total = nums.reduce((sum, num) => sum + num, 0);
if (total < 2 * k) return 0;
// dp[j] = number of ways to get sum j
const dp = new Array(k).fill(0);
dp[0] = 1;
for (const num of nums) {
for (let j = k - 1; j >= num; j--) {
dp[j] = (dp[j] + dp[j - num]) % MOD;
}
}
// Count partitions where first group sum < k
const bad = dp.reduce((sum, count) => (sum + count) % MOD, 0);
// Total partitions = 2^n
let totalPartitions = 1;
for (let i = 0; i < n; i++) {
totalPartitions = (totalPartitions * 2) % MOD;
}
// Answer = total - 2 * bad (by symmetry)
const ans = (totalPartitions - 2 * bad % MOD + MOD) % MOD;
return ans;
};
复杂度分析
| 复杂度 | 分析 |
|---|---|
| 时间复杂度 | O(n × k) |
| 空间复杂度 | O(k) |
说明:
- 时间复杂度:外层循环遍历n个元素,内层循环最多遍历k次,总时间复杂度为O(n × k)
- 空间复杂度:使用一维DP数组,长度为k,空间复杂度为O(k)