拆炸弹

标签: 数组 滑动窗口

难度: Easy

你有一个炸弹需要拆除,时间紧迫!你的情报员会给你一个长度为 n 的 循环 数组 code 以及一个密钥 k 。

为了获得正确的密码,你需要替换掉每一个数字。所有数字会 同时 被替换。

  • 如果 k > 0 ,将第 i 个数字用 接下来 k 个数字之和替换。
  • 如果 k < 0 ,将第 i 个数字用 之前 k 个数字之和替换。
  • 如果 k == 0 ,将第 i 个数字用 0 替换。

由于 code 是循环的, code[n-1] 下一个元素是 code[0] ,且 code[0] 前一个元素是 code[n-1] 。

给你 循环 数组 code 和整数密钥 k ,请你返回解密后的结果来拆除炸弹!

 

示例 1:

输入:code = [5,7,1,4], k = 3
输出:[12,10,16,13]
解释:每个数字都被接下来 3 个数字之和替换。解密后的密码为 [7+1+4, 1+4+5, 4+5+7, 5+7+1]。注意到数组是循环连接的。

示例 2:

输入:code = [1,2,3,4], k = 0
输出:[0,0,0,0]
解释:当 k 为 0 时,所有数字都被 0 替换。

示例 3:

输入:code = [2,4,9,3], k = -2
输出:[12,5,6,13]
解释:解密后的密码为 [3+9, 2+3, 4+2, 9+4] 。注意到数组是循环连接的。如果 k 是负数,那么和为 之前 的数字。

 

提示:

  • n == code.length
  • 1 <= n <= 100
  • 1 <= code[i] <= 100
  • -(n - 1) <= k <= n - 1

Submission

运行时间: 27 ms

内存: 16.2 MB

# class Solution:
#     def decrypt(self, code: List[int], k: int) -> List[int]:
#         n = len(code)
#         res = [0]*n
#         if k == 0:
#             return res
#         for i in range(n):
#             if k > 0:
#                 for j in range(i + 1,i + k + 1):
#                     res[i] += code[j % n]
#             elif k < 0:
#                 for j in range(i + k, i):
#                     res[i] += code[(j + n) % n]
#         return res


# 前缀和
class Solution:
    def decrypt(self, code: List[int], k: int) -> List[int]:
        n = len(code)
        ans = [0] * n
        if k == 0:
            return ans
        s = list(accumulate(code + code, initial=0))
        for i in range(n):
            if k > 0:
                ans[i] = s[i + k + 1] - s[i + 1]
            else:
                ans[i] = s[i + n] - s[i + k + n]
        return ans

Explain

题解使用了前缀和的概念来优化求区间和的操作。首先,构造一个新数组,该数组是原数组的两倍长,以方便处理循环数组的边界情况。然后,计算这个扩展数组的前缀和。当 k > 0 时,对于数组中的每个元素,其新值为从当前索引后移动 k 个位置的区间和;当 k < 0 时,其新值为从当前索引向前移动 |k| 个位置的区间和。前缀和数组允许以常数时间复杂度计算任意区间的和,大大提高了效率。

时间复杂度: O(n)

空间复杂度: O(n)

class Solution:
    def decrypt(self, code: List[int], k: int) -> List[int]:
        n = len(code)  # 原数组长度
        ans = [0] * n  # 初始化结果数组
        if k == 0:  # 当 k 为 0 时,直接返回全0的数组
            return ans
        s = list(accumulate(code + code, initial=0))  # 计算前缀和数组,扩展数组长度为2n
        for i in range(n):  # 遍历原数组每个元素
            if k > 0:  # 当 k > 0,计算i后面k个元素的和
                ans[i] = s[i + k + 1] - s[i + 1]  # 利用前缀和计算区间和
            else:  # 当 k < 0,计算i前面|k|个元素的和
                ans[i] = s[i + n] - s[i + k + n]  # 利用前缀和计算区间和,注意处理循环数组边界
        return ans

Explore

在处理k > 0的情况时,需要计算从当前索引i开始向右移动k个位置的区间和,因此区间的起始点是i+1,终点是i+k;而在处理k < 0的情况时,需要计算从当前索引i开始向左移动|k|个位置的区间和,因此区间的起始点是i+k+n,终点是i+n。这种不同的索引选择是为了正确地处理循环数组的边界并确保我们始终在扩展数组的有效范围内计算区间和。

将原数组长度扩展至两倍是为了简化循环数组边界的处理,确保无论是向前还是向后遍历时,都能在不超出数组边界的情况下访问到所有可能的元素。如果数组长度不足两倍,可能无法覆盖所有需要计算的区间;如果超过两倍,则会增加不必要的空间复杂度,因为更长的数组部分不会被实际用于计算任何新的区间和。

可以通过使用模运算来处理数组的索引,从而避免实际构造一个两倍长的数组。具体来说,可以直接在原数组上使用索引的方式,通过`(i + offset) % n`来访问数组元素,其中n是数组长度,offset是根据k计算的偏移量。这种方法可以有效地处理边界并且节省空间,但可能需要更多的计算来处理每次索引的模运算。

使用`initial=0`参数在计算前缀和时是为了在前缀和数组的开始添加一个0,这样可以方便地计算从数组起始位置到任意位置i的区间和。在没有这个初始0的情况下,要计算从数组起点到第一个元素的和时会更复杂,因为数组索引会向前偏移一位。有了这个初始值,前缀和数组的第i个元素直接表示原数组前i个元素的和。