【Hot100】560. 和为 K 的子数组

请添加图片描述

  • 🙋‍♂️ 作者:海码007
  • 📜 专栏:算法专栏
  • 💥 标题:【Hot100】560. 和为 K 的子数组
  • ❣️ 寄语:书到用时方恨少,事非经过不知难!

引言

和为 K 的子数组

  • 🎈 题目链接:https://leetcode.cn/problems/subarray-sum-equals-k/description/?envType=study-plan-v2&envId=top-100-liked
  • 🎈 做题状态:

我的解题

首先想到的是暴力解法,也就是循环两次,然后每次循环都查询满足条件的结果。这种方法超时。

  • 算法时间复杂度: O(n^2)
  • 空间复杂度:O(1)
class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        int count = 0;

        for (int i = 0; i < nums.size(); ++i)
        {
            int sum = 0;
            for (int j = i; j < nums.size(); ++j)
            {
                sum += nums[j];
                cout << "i = " << i << "; j = " << j << endl;
                if (sum == k)
                {
                    ++count;
                }
            }
        }

        return count;
    }
};

前缀和+哈希表 优化

思考:为什么可以使用前缀和优化?
这个题目是求子数组的和等于目标值的情况。然后前缀和相减可以计算任意子数组的和。例如已知前缀和 sum_i 和 sum_j 那么就可以通过 sum_j - sum_i 来计算 [i, j] 这个子数组的和。然后我们可以通过遍历一次计算出所有的前缀和。

思考:为什么使用哈希表?
计算完前缀和之后,我们要快速的查找 sum_j - k 的前缀和是否存在,此时就可以通过哈希表来优化查找效率。

核心问题

我们要找数组中所有 连续子数组的和等于k 的情况。例如,数组 [1,2,3],k=3,符合条件的子数组是 [1,2][3]


关键思路:前缀和

前缀和(Prefix Sum)是指从数组起始位置到当前位置所有元素的和。例如,数组 [1,2,3] 的前缀和依次是:

  • 到第0位:1
  • 到第1位:1+2=3
  • 到第2位:1+2+3=6

核心观察:如果前缀和到位置i的和是 sum_i,到位置j的和是 sum_j,那么子数组 [i+1, j] 的和就是 sum_j - sum_i

我们的目标是找到所有 sum_j - sum_i = k 的情况,即 sum_i = sum_j - k。如果能快速知道有多少个 sum_i 等于 sum_j - k,就能立刻知道有多少个子数组以j结尾且和为k。


哈希表的作用

为了快速查找 sum_i = sum_j - k 出现的次数,我们用哈希表存储所有出现过的前缀和及其出现的次数。

  • 键(Key):前缀和的值。
  • 值(Value):该前缀和出现的次数。

每次计算到当前前缀和 sum_j 时,我们检查哈希表中是否存在 sum_j - k

  • 如果存在,说明之前有某些位置i的前缀和是 sum_j - k,这些位置到当前位置j的子数组和就是k。
  • 将这些次数累加到结果中。

初始条件:prefixSumCount[0] = 1

这是为了解决从数组开头到当前位置的和正好是k的情况。例如,如果前缀和到j是k,那么 sum_j - k = 0,此时哈希表中必须已经存在一个前缀和0(即空数组的情况),才能正确计数。


举例说明

nums = [1,1,1], k = 2 为例:

  1. 初始化prefixSumCount = {0:1},当前和 currentSum = 0,结果 count = 0
  2. 遍历第一个元素1
    • currentSum = 0 + 1 = 1
    • 检查 1 - 2 = -1,哈希表中没有-1,结果不变。
    • 更新哈希表:{0:1, 1:1}
  3. 遍历第二个元素1
    • currentSum = 1 + 1 = 2
    • 检查 2 - 2 = 0,哈希表中有0,出现1次,所以 count += 1
    • 更新哈希表:{0:1, 1:1, 2:1}
  4. 遍历第三个元素1
    • currentSum = 2 + 1 = 3
    • 检查 3 - 2 = 1,哈希表中有1,出现1次,所以 count += 1
    • 更新哈希表:{0:1, 1:1, 2:1, 3:1}

最终结果 count = 2,对应子数组 [1,1][1,1](注意:实际正确答案应该是 [1,1][1,1],但根据输入数组为 [1,1,1],实际子数组是索引0-1和1-2)。


为什么能处理负数?

例如 nums = [3,4,7,2,-3,1,4,2], k = 7,前缀和可能出现重复或中间波动。但哈希表记录所有历史前缀和,无论正负,都能正确统计所有可能的情况。


总结

  1. 前缀和:计算到当前位置的总和。
  2. 哈希表:记录每个前缀和出现的次数。
  3. 查找目标:对于当前前缀和 sum_j,查找 sum_j - k 是否在哈希表中,如果在,则累加次数。
  4. 初始化:处理从数组开头开始的子数组。

这个算法将时间复杂度从 O(n²) 优化到 O(n),空间复杂度为 O(n),是一种典型的“空间换时间”策略。

代码

#include <vector>
#include <unordered_map>

class Solution {
public:
    int subarraySum(std::vector<int>& nums, int k) {
        // 哈希表:记录前缀和及其出现的次数
        // key = 前缀和, value = 该前缀和出现的次数
        std::unordered_map<int, int> prefixSumCount;
        
        // 初始化:前缀和为 0 出现 1 次(用于处理从数组开头开始的子数组和为k的情况)
        prefixSumCount[0] = 1;
        
        int count = 0;    // 记录满足条件的子数组数量
        int currentSum = 0; // 当前前缀和(从第0个元素到当前元素的和)

        // 遍历数组中的每个元素
        for (int num : nums) {
            // 更新当前前缀和(累加当前元素)
            currentSum += num;

            // 核心逻辑:如果存在前缀和等于 (currentSum - k),说明存在子数组和为k
            // 例如:假设 currentSum = 10,k = 3,则我们需要找的前缀和是 10-3=7
            // 如果哈希表中记录前缀和7出现过n次,说明有n个子数组的和为k(即从那些前缀和的位置到当前位置)
            if (prefixSumCount.find(currentSum - k) != prefixSumCount.end()) {
                count += prefixSumCount[currentSum - k];
            }

            // 将当前前缀和加入哈希表(如果已存在则次数+1,否则插入1)
            prefixSumCount[currentSum]++;
        }

        return count;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值