给你一个长度为 n
的整数数组 nums
,请你求出每个长度为 k
的子数组的 美丽值 。
一个子数组的 美丽值 定义为:如果子数组中第 x
小整数 是 负数 ,那么美丽值为第 x
小的数,否则美丽值为 0
。
请你返回一个包含 n - k + 1
个整数的数组,依次 表示数组中从第一个下标开始,每个长度为 k
的子数组的 美丽值 。
-
子数组指的是数组中一段连续 非空 的元素序列。
示例 1:
输入:nums = [1,-1,-3,-2,3], k = 3, x = 2 输出:[-1,-2,-2] 解释:总共有 3 个 k = 3 的子数组。 第一个子数组是[1, -1, -3]
,第二小的数是负数 -1 。 第二个子数组是[-1, -3, -2]
,第二小的数是负数 -2 。 第三个子数组是[-3, -2, 3] ,第二小的数是负数 -2 。
示例 2:
输入:nums = [-1,-2,-3,-4,-5], k = 2, x = 2 输出:[-1,-2,-3,-4] 解释:总共有 4 个 k = 2 的子数组。[-1, -2] 中第二小的数是负数 -1 。
[-2, -3] 中第二小的数是负数 -2 。
[-3, -4] 中第二小的数是负数 -3 。
[-4, -5] 中第二小的数是负数 -4 。
示例 3:
输入:nums = [-3,1,2,-3,0,-3], k = 2, x = 1 输出:[-3,0,-3,-3,-3] 解释:总共有 5 个 k = 2 的子数组。[-3, 1] 中最小的数是负数 -3 。
[1, 2] 中最小的数不是负数,所以美丽值为 0 。
[2, -3] 中最小的数是负数 -3 。
[-3, 0] 中最小的数是负数 -3 。
[0, -3] 中最小的数是负数 -3 。
提示:
n == nums.length
1 <= n <= 105
1 <= k <= n
1 <= x <= k
-50 <= nums[i] <= 50
class Solution:def getSubarrayBeauty(self, nums: List[int], k: int, x: int) -> List[int]:cnt = [0] * 101 # 用于统计窗口内数值的频率
为什么用一个长度为 101 的数组 cnt
?
- 范围映射:题目要求找负数,并且数组
nums
的取值范围为 [-50, 50]。为了高效统计每个元素在窗口内的频率,我们使用了一个大小为 101 的数组cnt
。- 索引映射:
cnt[0]
对应数值-50
,cnt[100]
对应数值50
,所有值都可以通过偏移量50
映射到数组中的索引位置。 - 频率数组的好处:使用频率数组,可以在 O(1) 时间复杂度内增减数值的频率。
- 索引映射:
for num in nums[:k - 1]: # 先往窗口添加 k-1 个数cnt[num + 50] += 1
为什么先预处理前 k-1
个数?
-
这样可以为后续的滑动窗口 节省计算时间。窗口初始化时,我们先将前
k-1
个元素加入cnt
频率数组中,这样在处理第一个完整窗口时,只需再加入第k
个元素即可开始计算。 -
num + 50
:- 由于数组中的元素范围是 [-50, 50],我们将每个元素加上 50,映射到频率数组中的正确位置。
ans = [0] * (len(nums) - k + 1) # 初始化结果数组
为什么结果数组长度是 len(nums) - k + 1
?
- 每个长度为
k
的窗口会对应一个美丽值,因此结果数组的长度是len(nums) - k + 1
。
for i, (in_, out) in enumerate(zip(nums[k-1:], nums)):cnt[in_ + 50] += 1 # 新元素进入窗口期
为什么使用 zip
和更新频率?
- 滑动窗口:
- 使用
zip(nums[k-1:], nums)
遍历数组时,in_
是 进入窗口的元素,out
是 离开窗口的元素。 - 当一个新元素进入窗口时,我们更新其频率:
cnt[in_ + 50] += 1
。
- 使用
left = x # 需要找到第 x 小的负数for j in range(-50, 0): # 枚举负数范围 [-50, -1]left -= cnt[j + 50] # 减去当前负数 j 的频率if left <= 0: # 如果找到了第 x 小的负数ans[i] = j # 记录美丽值break
为什么在负数范围 [-50, -1] 中查找?
- 美丽值要求找到 第
x
小的负数,因此我们只需要遍历负数的范围 [-50, -1]。 - 如何找到第
x
小的负数?- 使用一个计数变量
left
,初始值为x
。 - 遍历每个负数
j
的频率cnt[j + 50]
。每次找到一个符合条件的负数时,将其频率从left
中减去。 - 一旦
left <= 0
,说明我们已经找到了第x
小的负数,并将其记录到ans[i]
中。
- 使用一个计数变量
cnt[out + 50] -= 1 # 离开窗口的元素频率减 1
为什么离开窗口的元素需要减 1?
- 保持窗口内元素的正确频率:
- 当一个元素离开窗口时,我们需要在频率数组中减去它的出现次数,确保下一个窗口的频率统计是准确的。
return ans # 返回结果数组
完整代码
class Solution:def getSubarrayBeauty(self, nums: List[int], k: int, x: int) -> List[int]:cnt = [0]*101for num in nums[:k-1]: #先往窗口添加n个数cnt[num] += 1ans = [0]*(len(nums)-k+1)for i,(in_,out) in enumerate(zip(nums[k-1:],nums)): #建立滑动窗口cnt[in_] += 1 #进入窗口期 left = xfor j in range(-50,0): #暴力枚举负数范围[-50,-1]left -=cnt[j]if left<= 0: #找到美丽值ans[i] = jbreakcnt[out] -= 1 #离开窗口return ans
举例:
运行步骤:
-
初始化窗口:
前k-1
个元素[-1, -2]
被添加到频率数组中:cnt[-1 + 50] = 1 # cnt[49] = 1
cnt[-2 + 50] = 1 # cnt[48] = 1
-
第一个窗口
[ -1, -2, 3 ]
:- 新元素
3
加入窗口,频率数组更新:cnt[53] = 1
。 - 在负数范围 [-50, -1] 中查找第 2 小的负数:
- 负数
-2
出现 1 次,left = 2 - 1 = 1
。 - 负数
-1
出现 1 次,left = 1 - 1 = 0
。 - 找到第 2 小的负数为
-1
,记录ans[0] = -1
。
- 负数
- 新元素
-
第二个窗口
[ -2, 3, -1 ]
:- 新元素
-1
加入窗口,频率数组更新:cnt[49] = 2
。 - 负数
-2
和-1
都出现了,最终记录的美丽值仍然是-2
。
- 新元素