题目描述
小C得到了一个数组,他要对这个数组进行一些操作,直到数组为空。每次操作时他会执行以下步骤:
1.如果数组的第一个元素 等于,则直接删除 ,并将数组中剩余的所有元素向左移动以填补空缺。
2.否则,将 个 添加到数组未尾,并将 减少1。小C想知道,在进行这些操作直到数组为空时,总共进行了多少次操作。结果需要对 取模。
输入
n = 3, a = [2, 3, 4]
输出
257
算法
记忆化,递归
具体解题思路
我们可以将问题转化为计算每个数 x 到 0 所需的操作次数,并将该值缓存以避免重复计算。这一步可以使用递归函数 work(x) 来实现。
题目没有给出 a_i 的数据范围,所以使用递归函数 + 记忆化来求此结果。
思考思路
题目要求我们对数组进行一系列操作,直到数组为空。每次操作有两种可能性:
- 如果当前数组的第一个元素为 0:直接删除该元素并将数组左移。
- 如果当前元素不为 0:我们将
a[0] - 1个a[0] - 1添加到数组末尾,并将a[0]减少
这个过程会迅速增加数组的大小(尤其是当元素较大时),显然无法简单地进行逐个元素的模拟。因此,我们需要一种优化的策略来高效地计算完成这些操作的总次数,而不直接模拟整个操作过程。
解题难点
指数级增长的操作数:
每个非零元素会增加 a[0] - 1 个新元素到数组末尾,随着元素值的增加,数组的大小呈指数级增长。直接模拟这种操作是不可行的,因为操作次数会非常大,直接超出计算能力。
大数问题:
题目要求对结果取模 10^9 + 7,因此在实现时需要确保所有中间运算都在取模的范围内进行,防止溢出。
重复计算的优化:
对于每一个数值 x,当我们计算出其操作次数后,如果该数值在数组中多次出现,我们可以直接使用缓存的结果,而不必重新计算。因此需要一个记忆化存储策略来减少重复计算。
递归函数 work(x) 的定义
work(x) 表示从一个值 x 递减至 0 所需的操作次数。
-
递归边界:当
x == 0时,返回1,因为只需一次删除操作即可。 -
递归关系:
-
若
x > 0,则需要将x递减到0。在递归过程中:- 首先对
x本身操作一次(将x减少1)。 - 然后生成
x个x-1元素,意味着我们还需对每个生成的x-1进行work(x - 1)次操作。
- 首先对
-
总体公式为: work(x) = (x + 1) \times work(x - 1) + 1 公式中的
+1表示对当前x的一次操作,其余部分表示生成新元素所需的操作。
-
使用缓存来优化
为了避免重复计算,我们可以使用 map 来缓存 work(x) 的结果。这样当 work(x) 被多次调用时,直接使用缓存值即可,不必重新计算。
主函数 solution
在主函数 solution 中,遍历数组 a,对每个元素调用 work(a[i]),并累加每个元素从非零递减到零的总操作次数。
- 对每个元素
a[i],我们调用work(a[i])计算操作次数,将其累加到ans中,并在每次累加时对结果进行取模。 - 最终返回结果
ans,即为所有操作次数的总和。
代码分析
std::map<int, int> map; // 用于缓存每个元素的操作次数
constexpr int mod = 1e9 + 7;
// 递归函数 work(x) 计算每个值 x 从非零减少到零所需的操作次数
int work(int x) {
if (x == 0) {
return 1; // 如果 x 为 0,直接需要一次删除操作
}
if (map.count(x)) {
return map[x]; // 如果缓存中有 x 的操作次数,直接返回
}
// 否则递归计算 x 的操作次数
return map[x] = ((long long)(x + 1) * work(x - 1) + 1) % mod;
}
// 主解题函数
int solution(int n, std::vector<int> a) {
int ans = 0; // 存储总操作次数
for (int i = 0; i < a.size(); i++) {
ans = (ans + work(a[i])) % mod; // 累加每个元素的操作次数
}
return ans;
}
复杂度分析
时间复杂度:
- 使用了记忆化递归,
work(x)中每个值仅计算一次,故对于每个不同的x,只需O(x)的计算量。因此work(x)的复杂度是 线性的,即O(max(a)),其中max(a)是数组中最大的元素。 - 主函数
solution遍历数组a,整体时间复杂度为O(n + m),其中n为数组大小,m为work(x)中需要计算的唯一元素个数(即不同的a[i]值的数量)。
空间复杂度:
- 使用了
map来存储每个元素的操作次数,因此空间复杂度为O(m),其中m是数组a中不同的元素数量。
总结
- 难点在于递归定义每个数的操作次数,并通过缓存避免重复计算。
- 思路是递归地计算每个非零值到零的操作次数,并对结果取模控制结果大小。
- 时间复杂度约为
O(n + m),空间复杂度为O(m),能有效处理较大数组的情况。