题目链接:3. 无重复字符的最长子串
连续子数组问题,可以使用前缀和
而使用滑动窗口必须满足单调性!
方法1:前缀和+暴力枚举
- 统计前缀和
- 枚举前缀和数组的右端点,然后对于每个右端点,每次都枚举所有端点
java
class Solution {
public int subarraySum(int[] nums, int k) {
int n = nums.length;
int [] s = new int[n + 1];
//统计前缀和
for (int i = 0; i < n; i++){
s[i+1] = s[i] + nums[i];
}
int ans = 0;
for(int right = 1; right < n+1; right++){
int left = 0;
//暴力枚举
while (left < right){
if (s[right] - s[left] == k){
ans++;
}
left++;
}
}
return ans;
}
}
方法2:前缀和+哈希表
- 我们统计完前缀和之后,我们要找的是
s[j]-s[i]==k - 可以把问题转换成
s 中有多少对下标 (i,j) 满足 0≤i<j≤n 且 s[j]−s[i]=k - 写成
s[i]=s[j]-k,,这就是两数之和,利用哈希表"枚举右,维护左"- 枚举当前的前缀和
s[j],看看曾经有多少个前缀和等于s[j]−k(配对) - 比如
s[j]=2,那么s[i]=s[j]−k=2−1=1,我们要找的是j左边有多少个s[i]=1即可。这个操作可以使用哈希表
- 枚举当前的前缀和
写法1:两次遍历
java
class Solution {
public int subarraySum(int[] nums, int k) {
//统计前缀和
int n = nums.length;
int[] s = new int[n + 1];
for (int i = 0; i < n; i++) {
s[i + 1] = s[i] + nums[i];
}
//维护左,枚举右
Map<Integer,Integer> cnt = new HashMap<>();
int ans = 0;
for(int x : s){
ans += cnt.getOrDefault(x - k,0);
cnt.merge(x,1,Integer::sum);
}
return ans;
}
}
写法二:一次遍历
-
我们可以一边计算前缀和,一边遍历前缀和。
-
在遍历 nums 之前,我们需要先记录 s[0]=0,即空前缀的元素和等于 0。往 cnt 中添加 cnt[0]=1。
java
class Solution {
public int subarraySum(int[] nums, int k) {
Map<Integer, Integer> cnt = new HashMap<>(nums.length + 1, 1); // 预分配空间
cnt.put(0, 1); // s[0]=0 单独统计
int s = 0;
int ans = 0;
for (int x : nums) {
s += x;
ans += cnt.getOrDefault(s - k, 0);
cnt.merge(s, 1, Integer::sum); // cnt[s]++
}
return ans;
}
}
写法三:一次遍历
- 在同一轮循环中,先把 s [i −1] 加入哈希表,再根据 s [i] 更新答案。
- 这样写无需初始化 cnt[0]=1。
java
class Solution {
public int subarraySum(int[] nums, int k) {
Map<Integer, Integer> cnt = new HashMap<>(nums.length, 1); // 预分配空间
int s = 0;
int ans = 0;
for (int x : nums) {
cnt.merge(s, 1, Integer::sum); // cnt[s]++
s += x;
ans += cnt.getOrDefault(s - k, 0);
}
return ans;
}
}
如果这篇文章对你有帮助,欢迎点赞、评论、关注、收藏。你们的支持是我前进的动力!