Hard
题目描述
给你一个下标从 0 开始、由正整数组成的数组 nums 。
将数组分割成一个或多个 连续 子数组的分割方案是 好的 ,当且仅当没有两个子数组包含相同的数字。
返回 nums 中好分割方案的 总数 。
由于答案可能很大,请返回答案对 10^9 + 7 取余 的结果。
示例 1:
输入:nums = [1,2,3,4]
输出:8
解释:8 种可能的好分割方案是:([1], [2], [3], [4]), ([1], [2], [3,4]), ([1], [2,3], [4]), ([1], [2,3,4]), ([1,2], [3], [4]), ([1,2], [3,4]), ([1,2,3], [4]), 和 ([1,2,3,4])。
示例 2:
输入:nums = [1,1,1,1]
输出:1
解释:唯一可能的好分割方案是:([1,1,1,1])。
示例 3:
输入:nums = [1,2,1,3]
输出:2
解释:2 种可能的好分割方案是:([1,2,1], [3]) 和 ([1,2,1,3])。
提示:
1 <= nums.length <= 10^51 <= nums[i] <= 10^9
解题思路
这道题的核心思想是理解"好分割"的约束条件:如果一个子数组包含某个数字,那么它必须包含这个数字的所有出现位置。
解题思路
首先,我们需要找出每个数字的最后出现位置。对于任何包含数字 x 的子数组,它的结束位置至少要延伸到 x 的最后出现位置。
接下来,我们可以使用贪心策略找到所有必须的分割段:
- 维护当前段必须延伸到的最远位置
maxEnd - 从左到右扫描数组,对于每个位置的数字,更新
maxEnd为该数字最后出现位置 - 当到达
maxEnd时,说明找到了一个完整的段
找到所有必须的段后,问题转化为:如何将这些段进行分组?如果有 m 个必须的段,我们可以在相邻段之间选择是否插入分割点,共有 m-1 个可选分割点,因此总方案数为 2^(m-1)。
算法步骤
- 记录每个数字的最后出现位置
- 使用贪心策略找出所有必须的段
- 计算
2^(段数-1)作为答案
代码实现
class Solution {
public:
int numberOfGoodPartitions(vector<int>& nums) {
const int MOD = 1e9 + 7;
int n = nums.size();
// 记录每个数字的最后出现位置
unordered_map<int, int> lastPos;
for (int i = 0; i < n; i++) {
lastPos[nums[i]] = i;
}
// 找出所有必须的段
int segments = 0;
int maxEnd = -1;
for (int i = 0; i < n; i++) {
maxEnd = max(maxEnd, lastPos[nums[i]]);
if (i == maxEnd) {
segments++;
}
}
// 计算 2^(segments-1)
long long result = 1;
for (int i = 0; i < segments - 1; i++) {
result = (result * 2) % MOD;
}
return result;
}
};
class Solution:
def numberOfGoodPartitions(self, nums: List[int]) -> int:
MOD = 10**9 + 7
n = len(nums)
# 记录每个数字的最后出现位置
last_pos = {}
for i in range(n):
last_pos[nums[i]] = i
# 找出所有必须的段
segments = 0
max_end = -1
for i in range(n):
max_end = max(max_end, last_pos[nums[i]])
if i == max_end:
segments += 1
# 计算 2^(segments-1)
return pow(2, segments - 1, MOD)
public class Solution {
public int NumberOfGoodPartitions(int[] nums) {
const int MOD = 1000000007;
int n = nums.Length;
// 记录每个数字的最后出现位置
Dictionary<int, int> lastPos = new Dictionary<int, int>();
for (int i = 0; i < n; i++) {
lastPos[nums[i]] = i;
}
// 找出所有必须的段
int segments = 0;
int maxEnd = -1;
for (int i = 0; i < n; i++) {
maxEnd = Math.Max(maxEnd, lastPos[nums[i]]);
if (i == maxEnd) {
segments++;
}
}
// 计算 2^(segments-1)
long result = 1;
for (int i = 0; i < segments - 1; i++) {
result = (result * 2) % MOD;
}
return (int)result;
}
}
var numberOfGoodPartitions = function(nums) {
const MOD = 1000000007;
const n = nums.length;
// Find the last occurrence of each number
const lastOccurrence = new Map();
for (let i = 0; i < n; i++) {
lastOccurrence.set(nums[i], i);
}
let partitions = 0;
let maxEnd = -1;
for (let i = 0; i < n; i++) {
maxEnd = Math.max(maxEnd, lastOccurrence.get(nums[i]));
// If we've reached the end of current partition
if (i === maxEnd) {
partitions++;
}
}
// Number of good partitions is 2^(partitions-1)
if (partitions === 0) return 1;
let result = 1;
for (let i = 0; i < partitions - 1; i++) {
result = (result * 2) % MOD;
}
return result;
};
复杂度分析
| 复杂度类型 | 值 |
|---|---|
| 时间复杂度 | O(n) |
| 空间复杂度 | O(k) |
其中 n 是数组长度,k 是不同数字的个数。