题目描述
给你一个整数数组 nums
和两个整数 k
和 p
,找出并返回满足要求的不同的子数组数,要求子数组中最多 k
个可被 p
整除的元素。
如果满足下述条件之一,则认为数组 nums1
和 nums2
是 不同 数组:
- 两数组长度 不同,或者
- 存在 至少 一个下标
i
满足nums1[i] != nums2[i]
。
子数组 定义为:数组中的连续元素组成的一个 非空 序列。
样例
输入:nums = [2,3,3,2,2], k = 2, p = 2
输出:11
解释:
位于下标 0、3 和 4 的元素都可以被 p = 2 整除。
共计 11 个不同子数组都满足最多含 k = 2 个可以被 2 整除的元素:
[2]、[2,3]、[2,3,3]、[2,3,3,2]、[3]、[3,3]、[3,3,2]、[3,3,2,2]、[3,2]、[3,2,2] 和 [2,2]。
注意,尽管子数组 [2] 和 [3] 在 nums 中出现不止一次,但统计时只计数一次。
子数组 [2,3,3,2,2] 不满足条件,因为其中有 3 个元素可以被 2 整除。
输入:nums = [1,2,3,4], k = 4, p = 1
输出:10
解释:
nums 中的所有元素都可以被 p = 1 整除。
此外,nums 中的每个子数组都满足最多 4 个元素可以被 1 整除。
因为所有子数组互不相同,因此满足所有限制条件的子数组总数为 10。
限制
1 <= nums.length <= 200
1 <= nums[i], p <= 200
1 <= k <= nums.length
算法
(暴力枚举,字典树 / 哈希表) $O(n^2 \log L)$ 或 $O(n^2 L)$。
- 枚举子串开头位置 $i$,然后枚举子串结束位置 $j$,动态计算出当前子数组组成的字符串 $s$。
- 注意,组成 $s$ 的过程需要在每一个数字后添加一个特殊分隔符来区分,避免冲突。
- 如果哈希表中不存在 $s$,则答案加一,然后存入哈希表。
- 可以用字典树实现哈希表的功能,字典树节点的分支数量可以设为四个,三进制存储数字,剩一位存储特殊分隔符。
时间复杂度
- 共有 $O(n^2)$ 个子数组。
- 使用字典树优化分支数量,每个分支仅需要 $O(\log L)$ 的时间创建新分支。
- 而使用
unordered_set
则需要 $O(L)$ 的时间检查和存储。 - 故总时间复杂度为 $O(n^2 \log L)$ 或 $O(n^2 L)$。
空间复杂度
- 需要 $O(n^2 \log L)$ 或 $O(n^2L)$ 的额外空间存储字典树或哈希表。
C++ 代码
struct Node {
int ch[4];
Node() {
memset(ch, 0, sizeof(ch));
}
};
class Solution {
private:
vector<Node> node;
int node_cnt;
public:
int countDistinct(vector<int>& nums, int k, int p) {
const int n = nums.size();
node.resize(n * (n + 1) * 3 + 1);
node_cnt = 0;
int rt = node_cnt++;
int ans = 0;
for (int i = 0; i < n; i++) {
int cnt = 0, cur = rt;
for (int j = i; j < n; j++) {
if (nums[j] % p == 0)
cnt++;
if (cnt > k)
break;
int x = nums[j];
while (x) {
int t = x % 3;
if (!node[cur].ch[t])
node[cur].ch[t] = node_cnt++;
cur = node[cur].ch[t];
x /= 3;
}
if (!node[cur].ch[3]) {
ans++;
node[cur].ch[3] = node_cnt++;
}
cur = node[cur].ch[3];
}
}
return ans;
}
};