hot100—10.和为K的子数组

前置题目:请先完成前缀和模板题 303. 区域和检索 - 数组不可变,我的题解:前缀和及其扩展,附题单!

回顾一下 303 前缀和的定义:s[0]=0, s[i+1]=nums[0]+nums[1]+⋯+nums[i]。

设 i<j,如果 nums[i] 到 nums[j−1] 的元素和等于 k,用前缀和表示,就是

s[j]−s[i]=k
这个式子和 1. 两数之和 是类似的,仅仅由加法改成了减法。从两数之和的做法中,可以获得一些灵感,请看【动画】从两数之和中,我们可以学到什么?

枚举 j,上式变成

s[i]=s[j]−k
我们需要计算有多少个 s[i] 满足 i<j 且 s[i]=s[j]−k。换句话说:

已知 s[j] 和 k,统计 s[0] 到 s[j−1] 中有多少个数等于 s[j]−k。
我们可以在遍历 s[j] 的同时,用一个哈希表 cnt 统计 s[j] 的个数。那么枚举到 s[j] 时,从哈希表中就可以找到有 cnt[s[j]−k] 个 s[i],即为元素和等于 k 的子数组个数,加入答案。

以 nums=[1,1,−1,1,−1], k=1 为例,其前缀和 s=[0,1,2,1,2,1]。

j    s[j]    s[j]−k    在 s[j] 左边的 s[j]−k 的个数    解释
0    0    −1    0    无
1    1    0    1    s[0]=0
2    2    1    1    s[1]=1
3    1    0    1    s[0]=0
4    2    1    2    s[1]=s[3]=1
5    1    0    1    s[0]=0
用哈希表记录前缀和的个数,就能 O(1) 算出左边有多少个 s[j]−k。

总计有 0+1+1+1+2+1=6 个和为 k=1 的子数组。

答疑
问:为什么要把 s[0]=0 也加到哈希表中?

答:举个最简单的例子,nums=[1], k=1。如果不把 s[0]=0 加到哈希表中,按照我们的算法,没法算出这里有 1 个符合要求的子数组。也可以这样理解,要想把任意子数组都表示成两个前缀和的差,必须添加 s[0]=0,否则当子数组是前缀时,没法减去一个数,具体见 前缀和及其扩展 中的讲解。

问:为什么代码中要先更新 ans,再更新 cnt?这两行代码能否交换一下?

答:不行,这会在 k=0 的时候算错。例如 nums=[2], k=0,正确答案应该是 0,但如果先把 cnt[2] 加一,再把 cnt[2] 加到 ans 中,最后返回的 ans 就不是 0 了。

问:为什么这题不适合用滑动窗口做?

答:滑动窗口需要满足单调性,当右端点元素进入窗口时,窗口元素和是不能减少的。本题 nums 包含负数,当负数进入窗口时,窗口左端点反而要向左移动,导致算法复杂度不是线性的。

写法一:两次遍历
Python3
Java
C++
Go
JavaScript
Rust
class Solution:
    def subarraySum(self, nums: List[int], k: int) -> int:
        s = [0] * (len(nums) + 1)
        for i, x in enumerate(nums):
            s[i + 1] = s[i] + x

        ans = 0
        cnt = defaultdict(int)
        for sj in s:
            ans += cnt[sj - k]
            cnt[sj] += 1
        return ans
复杂度分析
时间复杂度:O(n),其中 n 为 nums 的长度。
空间复杂度:O(n)。
写法二:一次遍历
我们可以一边计算前缀和,一边遍历前缀和。

由于遍历 nums 会从 s[1] 开始计算,所以要单独处理 s[0]=0,也就是往 cnt 中添加 cnt[0]=1。

Python3
Java
C++
Go
JavaScript
Rust
class Solution:
    def subarraySum(self, nums: List[int], k: int) -> int:
        ans = s = 0
        cnt = defaultdict(int)
        cnt[0] = 1  # s[0]=0 单独统计
        for x in nums:
            s += x
            ans += cnt[s - k]
            cnt[s] += 1
        return ans
复杂度分析
时间复杂度:O(n),其中 n 为 nums 的长度。
空间复杂度:O(m),其中 m 为 s 中的不同元素个数。如果像 Java 那样设置了哈希表的容量,则空间复杂度为 O(n)。
变形题
改成计算元素和等于 k 的最短子数组,要怎么做?
改成计算元素和等于 k 的最长子数组,要怎么做?
改成计算元素和等于 k 的所有子数组的长度之和,要怎么做?
改成元素和至多为 k,要怎么做?见 363. 矩形区域不超过 K 的最大数值和。
欢迎在评论区分享你的思路/代码。

提示:思考题 4 可以枚举上下边界,转成一维数组。

作者:灵茶山艾府
链接:https://leetcode.cn/problems/subarray-sum-equals-k/solutions/2781031/qian-zhui-he-ha-xi-biao-cong-liang-ci-bi-4mwr/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

class Solution {

    public List<Integer> findAnagrams(String s, String p) {

        char[] P = p.toCharArray();

        Arrays.sort(P);

        String strP = new String(P);

        int pLength = P.length;

        int sLength = s.length();

        List<Integer> ans = new ArrayList<>();

        for(int i = pLength; i <= sLength;i++){

            String strS = s.substring(i-pLength,i);

            char[] S = strS.toCharArray();

            Arrays.sort(S);

            strS = new String(S);

            if(strP.equals(strS)){

                ans.add(i-pLength);

            }

        }

        return ans;

    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值