560. 和为K的子数组

博客提出一个问题,给定一个整数数组和整数k,需找出数组中和为k的连续子数组的个数。还给出了数组长度范围为[1, 20000],数组元素范围是[-1000, 1000],整数k范围是[-1e7, 1e7]。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

给定一个整数数组和一个整数 k,你需要找到该数组中和为 的连续的子数组的个数。

示例 1 :

输入:nums = [1,1,1], k = 2
输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。

说明 :

  1. 数组的长度为 [1, 20,000]。
  2. 数组中元素的范围是 [-1000, 1000] ,且整数 的范围是 [-1e7, 1e7]。
<think>好的,我现在需要帮助用户解决关于“半径为k的子数组平均值”算法实现的问题。首先,我得仔细理解用户的问题。用户提到了引用中的内容,特别是力扣的题目示例。用户可能是在做这道算法题,需要具体的实现方法。 首先,我要明确题目的要求。根据引用[2][4],题目要求对于数组中的每个位置i,计算以i为中心,半径为k的子数组的平均值。这个子数组的范围是从i-k到i+k,共2k+1个元素。如果i的前后不足k个元素,则返回-1。例如,当k=3时,中心位置i需要有至少3个元素在左边右边,否则无法计算。 接下来,我需要考虑如何高效地计算这些平均值。直接暴力解法是对于每个i,遍历i-k到i+k的元素求,然后取平均。但这样的话,时间复杂度会是O(n*(2k+1)),对于较大的nk来说,可能不够高效。用户提供的引用[5]中提到使用前缀数组来优化,这应该是关键点。 前缀数组可以将区间求的时间复杂度降到O(1)。具体来说,先构建一个前缀数组countArr,其中countArr[i]表示前i个元素的。这样,计算i-k到i+k的就可以通过countArr[i+k] - countArr[i-k-1]来得到。注意这里要考虑i-k是否为0的情况,避免越界。 然后,我需要处理边界情况。对于每个i,如果i < k或者i + k >= n(数组长度),则avg[i]为-1。否则,计算对应的区间,并除以2k+1。这里要注意数据类型,可能求会超过整数范围,所以要用长整型,如引用[5]中的long类型。 接下来,我需要考虑实现步骤。首先,构建前缀数组。然后遍历每个位置i,检查边界条件,然后计算区间,最后求平均。需要注意的是,除法可能需要截断处理,比如使用整数除法。 现在,我需要验证这个思路是否正确。以引用[4]中的示例,输入nums = [7,4,3,9,1,8,5,2,6],k=3。对于i=3(0-based),区间是0到6,共7个元素,求为37,除以7得5。这示例结果一致。同样,i=4时,区间是1到7,为32,32/7=4.571,但示例输出是4,说明确实用了截断除法。 在实现时,需要注意数组的索引是否正确。例如,当i=3,k=3时,左边界是0,右边界是6。前缀数组中countArr[6]是前7个元素的,而如果i-k=0,那么减去countArr[-1]会出错,所以需要处理i-k==0的情况,这时候区间就是countArr[i+k]。 另外,引用[5]的代码中,循环条件是从i=k到i < nums.length -k。这可能是因为在Java中数组的索引从0开始,当i >=k 且 i +k <n时,才有效。例如,数组长度为n,当i +k >=n时,右边界越界,所以循环条件是i <n -k。这样能确保i的左右都有k个元素。 现在,我需要将这些步骤转化为Python代码。Python中前缀数组可以用一个列表逐步累加。然后遍历每个i,判断是否在有效范围内,计算对应的区间。需要注意的是,Python的列表索引从0开始,所以当i-k >=0且i+k <n时,才有效。 同时,数据类型的处理,Python的int可以处理大数,所以不需要像Java那样显式转换,但为了正确性,还是应该确保求时使用足够大的类型。 现在,编写代码的大致步骤: 1. 计算前缀数组。 2. 初始化结果数组为全-1。 3. 遍历每个i,检查是否满足i >=k且i +k <n。 4. 如果满足,计算区间。如果i-k >0,则sum = prefix[i+k] - prefix[i-k-1],否则sum = prefix[i+k]。 5. 计算平均值,取整数部分。 6. 更新结果数组。 测试一下这个逻辑是否符合示例。例如,示例中的nums长度是9,k=3。有效i的范围是3到5(因为i+3 <9当i=5时,5+3=8 <9,i=5是最后一个有效的)。所以结果数组中只有3、4、5位置有值,其他为-1。计算i=3时,sum是prefix[6] - prefix[2]。假设prefix数组是正确的,这应该得到正确的结果。 现在,我需要考虑前缀数组的构建方式。通常,前缀数组的索引是前i+1个元素的。例如,prefix[0] = 0,prefix[1] = nums[0],prefix[2] = nums[0]+nums[1],等等。或者,另一种方式是prefix[i]表示前i个元素的,这样区间[i,j]的是prefix[j+1] - prefix[i]。例如,prefix[0]=0,prefix[1]=nums[0],prefix[2]=nums[0]+nums[1],这样区间a到b的是prefix[b+1]-prefix[a]。这可能更方便处理边界情况。 例如,对于nums数组长度n,前缀数组的长度是n+1,prefix[0]=0,prefix[i] = prefix[i-1] + nums[i-1]。这样,当计算i-k到i+k的时,对应的区间是原数组的i-k到i+k(闭区间),即前缀数组中的i-k到i+k的位置。因为原数组的索引是0-based,所以在前缀数组中,区间是prefix[i+k+1] - prefix[i -k]。但需要确保i -k >=0,即i >=k,并且i +k +1 <=n,即i +k <n → i <n -k。所以有效i的范围是k <=i <n -k。例如,当n=9,k=3时,i的范围是3 <=i <6(即3,4,5)。 因此,前缀数组的这种构建方式可能更清晰,避免i-k-1的情况。例如,当i=3,k=3,i-k=0,那么区间是prefix[7] - prefix[0],对应原数组的0到6号元素,共7个元素。这与示例中的情况一致。因此,这样的前缀结构可能更容易处理。 综上,正确的步骤应该是: 1. 构建前缀数组prefix,长度为n+1,prefix[0]=0,prefix[i] = prefix[i-1] + nums[i-1]。 2. 遍历每个i,从0到n-1: a. 检查i是否满足左有k元素,右有k元素。即i -k >=0 → i >=k,且i +k <n → i <n -k → i <=n-1 -k → i <=n-1 -k → 因为i是0-based,所以右边界i+k <=n-1 → i <=n-1 -k → 所以有效的i范围是k <=i <=n-1 -k → 即i的取值范围是[k, n-1 -k] b. 对于有效的i,计算sum = prefix[i+k+1] - prefix[i -k] c. 计算avg = sum // (2k +1) d. 将avg存入结果数组的i位置 3. 其他位置的值为-1 这样,在示例中,当i=3,k=3,n=9时,i >=3且3+3=6 <9 → 有效。sum=prefix[3+3+1] - prefix[3-3] = prefix[7] - prefix[0]。prefix[7]是前7个元素的,即nums[0]到nums[6],即7+4+3+9+1+8+5=37,除以7得到5,正确。 这样看来,这种构建方式更准确。那么代码应该如何编写? 在Python中: n = len(nums) prefix = [0] * (n +1) for i in range(1, n+1): prefix[i] = prefix[i-1] + nums[i-1] avgs = [-1]*n window_size = 2*k +1 for i in range(n): if i >=k and i +k <n: left = i -k right = i +k total = prefix[right +1] - prefix[left] avg = total // window_size avgs[i] = avg 这样处理是否正确?是的。例如,当i=3,k=3,n=9: i >=3 → 是,i +k=6 <9 → 是。left=0,right=6。prefix[right+1] = prefix[7],prefix[left]=prefix[0]=0。总是37,除以7得5。正确。 另一个示例中的i=4: left=1,right=7 → prefix[8] - prefix[1] = sum(nums[1]到nums[7]) → 4+3+9+1+8+5+2=32 →32/7=4.571 →取整4,正确。 同样i=5: left=2, right=8 → sum(nums[2]到nums[8])=3+9+1+8+5+2+6=34 →34/7=4.857 →4,正确。 因此,这种方法是正确的。而用户提供的引用[5]中的代码可能使用了不同的前缀方式,即前缀数组是原数组的累加,而不是从0开始。例如,countArr[i] = nums[0]+...+nums[i]。因此,当计算i+ki-k-1的时候,区间是countArr[i+k] - countArr[i-k-1]。这需要i-k-1 >=0,否则直接用countArr[i+k]。这我们刚才讨论的两种前缀构建方式的不同有关。两种方式都是可行的,但需要正确处理索引。 例如,在引用[5]的Java代码中: countArr[i] = nums[0] + ... + nums[i] 所以,当计算i-k到i+k的: 如果i-k >0 → left = i-k-1,则sum = countArr[i+k] - countArr[left] 否则,sum = countArr[i+k] 例如,当i=3,k=3时,i-k=0 → left = i-k-1= -1,所以sum = countArr[6](即nums[0]+...+nums[6]) →正确。 而当i=4,k=3,i-k=1 → left=0 → sum = countArr[7] - countArr[0] → nums[1]到nums[7]的,正确。 所以,两种方法都是正确的。用户的问题可能更关注如何正确实现,所以我需要提供一个Python的实现,并确保正确处理边界。 综上,正确的Python实现应该使用前缀数组,并正确处理窗口的边界条件。此外,需要注意数据类型,避免溢出,但在Python中这可能不是问题。所以,最终的代码大致如下: def get_averages(nums, k): n = len(nums) prefix = [0] * (n + 1) for i in range(n): prefix[i+1] = prefix[i] + nums[i] avgs = [-1] * n window_size = 2*k +1 for i in range(n): if i >= k and i + k < n: left = i - k right = i + k total = prefix[right +1] - prefix[left] avg = total // window_size avgs[i] = avg return avgs 这样,测试示例输入nums = [7,4,3,9,1,8,5,2,6], k=3时,输出应该为[-1,-1,-1,5,4,4,-1,-1,-1],与题目一致。 现在,用户可能需要了解时间复杂度。这个实现的时间复杂度是O(n),因为构建前缀数组是O(n),然后遍历也是O(n)。空间复杂度是O(n)用于存储前缀数组。 此外,用户可能关心如何处理大数,但Python的int可以处理大整数,所以无需担心溢出问题。不过,在其他语言如Java中,可能需要使用long类型来存储前缀。 最后,用户可能需要了解其他优化方法,例如滑动窗口,在窗口移动时维护当前的,从而避免每次重新计算。例如,当窗口向右移动时,减去左边离开窗口的元素,加上右边进入窗口的元素。这在某些情况下可能更高效,特别是当k较大时。但对于这个问题,前缀的方法已经是O(n)的时间复杂度,滑动窗口可能不会有显著的性能提升,但可以节省空间,因为不需要存储整个前缀数组,只需要维护当前窗口的。 例如,滑动窗口的实现: 初始化sum为0,窗口的有效中心i必须满足i >=k且i <n -k。对于第一个有效的i=k,需要计算初始窗口的(i=0到2k)。然后,每次i增加1时,窗口左边界右移一个(减去nums[i -k -1]),右边界右移一个(加上nums[i +k])。这样,每次更新sum只需要O(1)时间。 这可能在某些情况下更高效,尤其是当内存受限时。但实现时需要注意初始窗口的构建是否正确。 例如,滑动窗口实现的步骤: 1. 初始化结果数组为全-1。 2. 计算窗口大小window_size = 2k+1。 3. 如果n < window_size,直接返回全-1。 4. 计算初始窗口的sum = sum(nums[0..2k])。 5. 结果数组的第k位置的avg是sum // window_size。 6. 然后,从i=k+1到i <n -k,每次sum减去nums[i -k -1],加上nums[i +k],然后计算avg。 7. 更新对应的结果位置。 这种方法的时间复杂度同样是O(n),但空间复杂度是O(1)(除了结果数组)。这在n很大时可能更节省内存。 例如,在Python中实现滑动窗口: def get_averages(nums, k): n = len(nums) avgs = [-1] * n window_size = 2*k +1 if window_size > n: return avgs current_sum = sum(nums[:window_size]) avgs[k] = current_sum // window_size for i in range(k+1, n -k): current_sum += nums[i +k] - nums[i -k -1] avgs[i] = current_sum // window_size return avgs 但需要注意,这种方法是否正确?例如,当i=k时,窗口是0到2k,对吗?是的。例如,当k=3,窗口是0到6,共7个元素。然后,当i增加到k+1=4时,窗口变为1到7,sum减去nums[0],加上nums[7]。这样是否正确? 是的,这样每次移动窗口,左边移除一个元素,右边新增一个元素。因此,总可以正确计算。这种方法的时间复杂度是O(n),因为初始sum需要O(window_size)时间,而之后每个i的处理是O(1)。 当k较大的时候,比如k接近n/2,此时window_size接近n,初始sum需要O(n)时间,整个算法的时间复杂度还是O(n)。所以,两种方法的时间复杂度相同,但滑动窗口可能在k较小时更高效,而当k很大时,可能前缀法相当。不过,在Python中,sum(nums[:window_size])的时间可能比构建前缀数组要慢,尤其是当window_size很大时。 但滑动窗口的优势在于不需要额外的空间存储前缀数组。例如,当nums数组很大时,这可能节省内存。 因此,用户可能需要了解这两种方法的不同,并根据具体情况选择。例如,在内存受限的情况下,滑动窗口更好;而当需要多次查询不同区间的时,前缀数组更有优势。 综上,我需要向用户解释这两种方法的实现,并给出代码示例,同时分析它们的优缺点。 现在,总结用户的问题,他需要的是实现半径为k的子数组平均值的算法。因此,我应该给出基于前缀数组的Python实现,并解释其步骤,同时可能提及滑动窗口的方法作为优化选项。</think>### 半径为 k 的子数组平均值算法实现 #### 核心思路 计算每个位置 $i$ 的半径为 $k$ 的子数组平均值时,需满足左右各 $k$ 个元素的条件。采用**前缀数组**优化求过程,将时间复杂度从 $O(n \cdot k)$ 降低到 $O(n)$[^2][^4]。 #### 算法步骤 1. **构建前缀数组** 创建长度为 $n+1$ 的前缀数组 `prefix`,其中 `prefix[i]` 表示前 $i$ 个元素的: $$prefix[i] = \sum_{j=0}^{i-1} nums[j]$$ 2. **遍历计算平均值** 对于每个位置 $i$,若满足 $i \geq k$ 且 $i + k < n$,则计算区间: $$sum = prefix[i+k+1] - prefix[i-k]$$ 平均值为: $$avg = \left\lfloor \frac{sum}{2k+1} \right\rfloor$$ 3. **处理边界条件** 不满足条件的下标直接赋值为 $-1$。 #### Python实现代码 ```python def get_averages(nums, k): n = len(nums) prefix = [0] * (n + 1) for i in range(n): prefix[i + 1] = prefix[i] + nums[i] avgs = [-1] * n window_size = 2 * k + 1 for i in range(n): if i >= k and i + k < n: total = prefix[i + k + 1] - prefix[i - k] avgs[i] = total // window_size return avgs ``` #### 示例验证 输入:`nums = [7,4,3,9,1,8,5,2,6], k = 3` 输出:`[-1,-1,-1,5,4,4,-1,-1,-1]` **计算过程**: - `i=3` 时,总为 $7+4+3+9+1+8+5=37$,$37/7=5$ - `i=4` 时,总为 $4+3+9+1+8+5+2=32$,$32/7=4$ - `i=5` 时,总为 $3+9+1+8+5+2+6=34$,$34/7=4$[^4] #### 优化方法 使用**滑动窗口**进一步降低空间复杂度(无需存储前缀数组): ```python def get_averages_optimized(nums, k): n = len(nums) avgs = [-1] * n window_size = 2 * k + 1 if window_size > n: return avgs current_sum = sum(nums[:window_size]) avgs[k] = current_sum // window_size for i in range(k + 1, n - k): current_sum += nums[i + k] - nums[i - k - 1] avgs[i] = current_sum // window_size return avgs ``` #### 复杂度分析 | 方法 | 时间复杂度 | 空间复杂度 | 适用场景 | |--------------|------------|------------|------------------------| | 前缀数组 | $O(n)$ | $O(n)$ | 通用,多次查询不同区间 | | 滑动窗口 | $O(n)$ | $O(1)$ | 内存受限场景 |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值