longlongzihan头像
关注
LeetCode 560. 和为 K 的子数组:从暴力枚举到哈希表前缀和优化封面图

LeetCode 560. 和为 K 的子数组:从暴力枚举到哈希表前缀和优化

1. 题目描述(题目链接)

给你一个整数数组 nums 和一个整数 k,请你统计并返回该数组中和为 k 的子数组的个数。

注意:

子数组是数组中元素的连续非空序列。

数组中可能包含负数。

数据范围:

示例:

输入:nums = [1, 1, 1], k = 2
输出:2
解释:共有两个子数组的和为 2,分别是 [1, 1] (下标 0-1) 和 [1, 1] (下标 1-2)。

2. 解法一:暴力枚举

2.1 核心思想

最直观的思路是:枚举所有的子数组,并计算它们的和。
我们可以使用两层循环:

  1. 外层循环 i 枚举子数组的左边界。

  2. 内层循环 j 枚举子数组的右边界。

  3. 在内层循环中,维护一个变量 sum,累加从 i 到 j 的元素。如果 sum == k,则计数器加一。

2.2 代码实现 (C++)

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        int ans = 0;
        int n = nums.size();
        
        // 枚举左边界 i
        for (int i = 0; i < n; i++) {
            int sum = 0; // 每次更换左边界,重置 sum
            // 枚举右边界 j
            for (int j = i; j < n; j++) {
                sum += nums[j]; // 累加当前元素
                if (sum == k) {
                    ans++;
                }
                // 注意:这里不能因为 sum > k 就 break,因为数组中存在负数
            }
        }
        return ans;
    }
};

2.3 复杂度分析

时间复杂度:O(N2)。有两层嵌套循环

空间复杂度:O(1)。只使用了常数级别的额外空间。

3. 解法二:前缀和 + 哈希表(最优解)

暴力解法的时间瓶颈在于:对于每个右边界 j,我们都重新计算了从 i 到 j 的和。我们可以利用前缀和来优化这个计算过程。

3.1 核心推导

定义 preSum[i] 为数组从第 0 个元素到第 i 个元素的总和。
那么,子数组 nums[i...j] 的和可以表示为:

Sum(i,j)=preSum[j]−preSum[i−1]

题目要求 Sum(i, j) == k,代入公式得:

preSum[j]−preSum[i−1]=k

移项得:

preSum[i−1]=preSum[j]−k

当我们遍历到位置 j 时,我们只需要知道在 j 之前,有多少个前缀和等于 preSum[j] - k 即可。

3.2 哈希表的作用

为了快速查询“之前出现过多少次某个前缀和”,我们可以使用哈希表 (unordered_map)。

Key:前缀和的值。

Value:该前缀和出现的次数。

3.3 细节处理

  1. 初始化 map[0] = 1:这是为了处理从数组起始位置 (索引 0) 开始的子数组。如果 preSum[j] 本身就等于 k,那么 preSum[j] - k = 0,此时我们需要哈希表里存在 0 这个前缀和,且次数为 1。

  2. 边遍历边更新:先计算当前 preSum,去哈希表里查找 preSum - k 的次数累加到 ans,然后再把当前的 preSum 放入哈希表。顺序不能颠倒,否则会把自己也算进去,导致错误。

3.4 代码实现 (C++)

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        unordered_map<int, int> mp;
        // 初始化,前缀和为0的情况出现了1次
        mp[0] = 1;
        
        int preSum = 0; // 记录当前前缀和
        int ans = 0;    // 记录结果
        
        for (int i = 0; i < nums.size(); i++) {
            preSum += nums[i]; // 计算到当前位置的前缀和
            
            // 核心逻辑:如果 (当前前缀和 - k) 在哈希表中存在
            if (mp.find(preSum - k) != mp.end()) {
                ans += mp[preSum - k];
            }
            
            // 将当前前缀和存入哈希表
            mp[preSum]++;
        }
        
        return ans;
    }
};

3.5 复杂度分析

时间复杂度:O(N)。只需要遍历一次数组,哈希表的插入和查找操作平均时间复杂度为 O(1)。

空间复杂度:O(N)。最坏情况下,数组中的每个元素都会产生不同的前缀和,哈希表需要存储 N 个键值对。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/longlongzihan/article/details/167178757

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--