数组操作次数计算 | 豆包MarsCode AI刷题

124 阅读5分钟

题目描述

小C得到了一个数组,他要对这个数组进行一些操作,直到数组为空。每次操作时他会执行以下步骤:
1.如果数组的第一个元素 a[0]a[0]等于00,则直接删除 a[0]a[0],并将数组中剩余的所有元素向左移动以填补空缺。
2.否则,将 a[0]a[0]a[0]1a[0]-1 添加到数组未尾,并将 a[0]a[0]减少1。小C想知道,在进行这些操作直到数组为空时,总共进行了多少次操作。结果需要对109+710^9 + 7 取模。

输入

n = 3, a = [2, 3, 4]

输出

257

算法

记忆化,递归

具体解题思路

我们可以将问题转化为计算每个数 x 到 0 所需的操作次数,并将该值缓存以避免重复计算。这一步可以使用递归函数 work(x) 来实现。

题目没有给出 a_i 的数据范围,所以使用递归函数 + 记忆化来求此结果。

思考思路

题目要求我们对数组进行一系列操作,直到数组为空。每次操作有两种可能性:

  1. 如果当前数组的第一个元素为 0:直接删除该元素并将数组左移。
  2. 如果当前元素不为 0:我们将 a[0] - 1a[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)。
      • 然后生成 xx-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 为数组大小,mwork(x) 中需要计算的唯一元素个数(即不同的 a[i] 值的数量)。

空间复杂度

  • 使用了 map 来存储每个元素的操作次数,因此空间复杂度为 O(m),其中 m 是数组 a 中不同的元素数量。

总结

  • 难点在于递归定义每个数的操作次数,并通过缓存避免重复计算。
  • 思路是递归地计算每个非零值到零的操作次数,并对结果取模控制结果大小。
  • 时间复杂度约为 O(n + m),空间复杂度为 O(m),能有效处理较大数组的情况。