标签:get 保存 哈希表 ISE 整数 pre turn 包含 +=
前缀和 就是数组 第 0 项 到 当前项 的总和。比如数组 nums
,那么它的前缀和 prefixSum[x]
就表示 nums
从第 0 项到第 x 项的总和。
p
r
e
f
i
x
S
u
m
[
x
]
=
n
u
m
s
[
0
]
+
n
u
m
s
[
1
]
+
.
.
.
+
n
u
m
s
[
x
]
prefixSum[x] = nums[0] + nums[1] + ...+ nums[x]
prefixSum[x]=nums[0]+nums[1]+...+nums[x]
n
u
m
s
[
x
]
=
p
r
e
f
i
x
S
u
m
[
x
]
?
p
r
e
f
i
x
S
u
m
[
x
?
1
]
nums[x] = prefixSum[x] - prefixSum[x-1]
nums[x]=prefixSum[x]?prefixSum[x?1]
n
u
m
[
i
]
+
.
.
.
+
n
u
m
[
j
]
=
p
r
e
f
i
x
S
u
m
[
j
]
?
p
r
e
f
i
x
S
u
m
[
i
?
1
]
num[i] + ... + num[j] = prefixSum[j] - prefixSum[i-1]
num[i]+...+num[j]=prefixSum[j]?prefixSum[i?1]<br> 特别注意,由于 `i` 可以为 0,`i-1` 会出现数组越界,所以前缀和数组一般需要在 0 位置扩充一个 0,便于计算数组 nums 中第 0 项到第 k 项之和。
前缀和数组每一项 dp[i]
都是原数组从第 0
项到第 i
项的总和,所以如果 j
大于 i
,那么 dp[j]
就一定是包含 dp[i]
的。
思路一:暴力解法,双重循环,每次固定一个 i
,用 j
循环往前走,每到一个位置判断子数组和是否为 target,是则 count + 1。时间复杂度为
O
(
n
2
)
O(n^2)
O(n2),会超时。
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
if not nums: return 0
n = len(nums)
count = 0
for i in range(n):
sum = 0
for j in range(i, -1, -1):
sum += nums[j]
if sum == k: count += 1
return count
思路二:前缀和解法。假设目标值为 k,对于任意子数组的和(假设为 x),为了使得这个子数组和能满足目标值 k,就要找另一个子数组的和(假设为 y),使得 y - x = k。
可以创建一个与原数组大小相同的新数组 dp
,即前缀和数组。该数组每个位置上都保存了原数组从 0 到对应位置的所有元素的和。从后向前遍历前缀和数组,每到一个位置,如果不等于目标值 target
,则向前寻找是否有 dp[j] - target
,找到一个 count 数就加 1。
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
n = len(nums)
prefix = [0] # 前缀和数组要额外加一个0
# 创建前缀和数组
for i in range(n):
prefix.append(prefix[i] + nums[i])
# 遍历计数
count = 0
for i in range(n, 0, -1):
target = prefix[i] - k
for j in range(i-1, -1, -1):
if prefix[j] == target:
count += 1
return count
这样遍历其实仍然是双重循环,时间复杂度并没有降低,运行一下依然超时。
思路二改进:考虑用空间换时间,使用 前缀和 + 哈希表。用哈希表存储前缀和,键为 “前缀和的值”,值为 “该前缀和的出现次数”。从左向右遍历前缀和数组,将当前前缀和的值存入哈希表中,如果已经存在则直接加 1,并在哈希表中查找是否有键等于 “当前前缀和 - target”,若存在则给计数器 count 加上该键对应的值。
设 j > i
,则我们的目标是 prefix[j] - prefix[i] = target
:
j
,在哈希表里的查找目标就是 pre[i] = pre[j] - target
- 如果从右向左遍历前缀和数组,每次来到的位置都是 i
,在哈希表里的查找目标就是 pre[j] = target + pre[i]
```
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
n = len(nums)
prefix = [0]
count, dict = 0, {<!-- -->0:1}
# 从左向右遍历
for i in range(n):
pre = prefix[i] + nums[i]
prefix.append(pre)
# 查找目标值,若无则返回-1
c = dict.get(pre - k, -1) # 查找目标pre[i] = pre[j]-target
if c != -1:
count += c
# 当前前缀和添加到哈希表(注意先找目标值,再添加到哈希表)
if pre not in dict:
dict[pre] = 1
else:
dict[pre] += 1
return count
只需要一层循环,时间复杂度为 O(n)。用哈希表加速运算的思路适合 <mark>不关心具体的解,仅关心解的个数</mark> 的情况。
[1248. 统计「优美子数组」](https://leetcode-cn.com/problems/count-number-of-nice-subarrays/)
**思路:前缀和 + 差分**。约定数组 `pre` 中 `pre[i]` 表示以第 `i` 个元素为结尾的子数组 `[nums[0]...nums[i]]` 中奇数的个数,那么 `pre[i]` 可以由前置状态 `pre[i-1]` 计算得到,即:`pre[i] = pre[i-1] + 1 if nums[i] 是奇数 else pre[i-1]`。
那么题目要求 `[j...i]` 这个子数组的奇数个数恰好为 k,可以将这个条件转化为 `pre[i] - pre[j-1] == k`。
在实际求解时,从左向右计算 `pre` 数组中各个位置的状态(即奇数个数),然后将这个个数登记在哈希表里,生成键值对:“奇数个数-出现次数”,每次登记时,顺便在哈希表看一看有没有目标值 `pre[j-1]`,如果有就可以将个数累加在最终的答案 sum 上。`pre` 数组并不需要真的建立,只需要维护前一个值即可。
**时间复杂度**:
O
(
n
)
O(n)
O(n),遍历长度为 n 的数组<br> **空间复杂度**:
O
(
n
)
O(n)
O(n),需要哈希表记录奇数出现频次,最坏情况下哈希表长度等于原数组
class Solution: def numberOfSubarrays(self, nums: List[int], k: int) -> int: n = len(nums) pre = 0 res, dict = 0, {0:1} for i in range(n): pre = pre + 1 if nums[i] % 2 != 0 else pre dict[pre] = dict.get(pre, 0) + 1 res += dict.get(pre - k, 0) return res
[长度最小的子数组](https://leetcode-cn.com/problems/minimum-size-subarray-sum/):给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组,并返回其长度。如果不存在符合条件的连续子数组,返回 0.
**思路:前缀和 + 二分查找**。额外创建一个数组用于存储原数组的前缀和,由于题目规定原数组均为正整数,所以该前缀和数组可保证是递增的。前缀和数组 `sums[i]` 表示从 `nums[0]` 到 `nums[i-1]` 的元素和。对于每个元素下标,可通过二分查找得到一个大于或等于 `i` 的最小下标 `j`,使得 `sums[j] - sums[i-1] >= s`,此时更新新子数组的最小长度。
**时间复杂度**:
O
(
n
l
o
g
n
)
O(nlog_n)
O(nlogn?)<br> **空间复杂度**:
O
(
n
)
O(n)
O(n)
class Solution: def minSubArrayLen(self, s: int, nums: List[int]) -> int: if not nums: return 0
n = len(nums)
ans = n + 1
sums = [0]
for i in range(n):
sums.append(sums[-1] + nums[i])
for i in range(1, n + 1):
target = s + sums[i - 1]
bound = bisect.bisect_left(sums, target) # 二分查找
if bound != len(sums):
ans = min(ans, bound - (i - 1))
return 0 if ans == n + 1 else ans
另外,最开始写的暴力法肯定会超时:
class Solution: def minSubArrayLen(self, s: int, nums: List[int]) -> int: n = len(nums) dp = [n+1 for _ in range(n)] res = n+1 for i in range(n): cur = 0 for j in range(i,-1,-1): cur += nums[j] if cur >= s: res = min(res, i-j+1) if res < n+1: return res else: return 0
```
标签:get 保存 哈希表 ISE 整数 pre turn 包含 +=
原文地址:https://www.cnblogs.com/hy627/p/14240644.html