leetcode-和为K的子数组之hash表(前缀和)

本文探讨了子数组和问题的暴力破解与优化算法。暴力破解时间复杂度为O(n^2),通过创建hash表记录前缀和,优化算法将时间复杂度降低至O(n)。文章提供了详细的代码示例,展示了如何利用hash表提高查找效率。

题目

在这里插入图片描述
暴力破解的时间复杂度为O(n^2),绝逼超时,代码如下:

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        map<int,int> m;
        for(int i=0;i<nums.size();i++)
        {
            int sum = 0;
            for(int j=0;j<=i;j++)
            {
                sum += nums[j];
            }
            m.insert({i+1,sum});
        }
        int count = 0;
        m.insert({0,0});
        
        for(int i=0;i<m.size();i++)
        {
            for(int j=i+1;j<m.size();j++)
            {
                
                if(m[j]-m[i] == k)
                count++;
            }
        }
        return count;
    }
};

这类问题的解法,在于一边计算一个元素的前缀和的时候,还要记录产生了哪些前缀和,也就是要为每个产生的前缀和创建hash表;
若是一个前缀和减去要找的数的结果也存在于hash表中,说明存放和为要找的结果的连续子序列;
而这题因为是求一个连续和出现的次数,因此以前缀和的次数作为hash的value;
题目还可以这么问,“给定一个整数数组和一个整数 k,你需要找到该数组中是否存在和为 k 的连续的子数组”;
代码如下:

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        unordered_map<int,int> m;
        m[0] = 1;
        int sum = 0;
        int count = 0;
        for(int i=0;i<nums.size();i++)
        {
            sum += nums[i];
            
            if(m.find(sum-k)!=m.end())
            count += m[sum-k];
            m[sum] ++;
        }
        
        return count;
    }
};
<think>好的,我现在需要帮助用户解决如何用C++实现求为k的连续子数组的问题。用户已经提供了几个引用,我需要仔细分析这些引用内容,并结合自己的知识来给出正确的答案。 首先,用户的问题是关于LeetCode上的“为K的子数组”问题,要求用C++实现。根据引用[2]引用[3],这个问题可以通过前缀和加哈希的方法来高效解决,时间复杂度为O(n)。而引用[4]也提到了类似的方法,但可能更侧重于最长子数组长度,不过基本原理是相通的。 我需要先理解这个算法的思路。前缀和数组pre[i]示前i个元素的,那么要找的是满足pre[j] - pre[i] = k的ij,也就是子数组为k。转化为pre[j] - k = pre[i],所以可以遍历数组时,计算当前的前缀和sum,并检查哈希中是否存在sum - k的键。如果存在,说明存在某个之前的pre[i]等于sum -k,那么j-i之间的子数组就是k。 接下来,我需要确保代码的正确性。引用[2]中的C++代码是一个示例,使用了unordered_map来记录前缀和出现的次数。初始化时,哈希中预先存入hash[0] = 1,这是为了处理从数组开头到当前位置的正好是k的情况。例如,如果sum等于k,那么sum -k =0,此时哈希中已经有0的出现次数,可以正确累加到ret中。 另外,引用[3]提到暴力解法的时间复杂度是O(),而哈希优化后是O(n)。这说明哈希方法更高效,所以应该采用这种方法。 然后,我需要检查用户提供的引用是否存在需要注意的地方。例如,引用[4]中的代码是用JavaScript写的,但思路类似,不过它计算的是最长子数组长度,而用户的问题是需要统计所有可能的子数组数量。因此,在实现时需要区分这两种情况,确保哈希中存储的是前缀和出现的次数,而不是最左端的索引。 现在,我需要将思路整理成步骤,确保每一步都清晰。首先初始化哈希,然后遍历数组,计算前缀和,检查哈希中是否有sum -k的键,更新结果,最后将当前前缀和存入哈希。注意,每次循环都需要先检查,再更新哈希,以避免重复计数。 代码部分需要正确使用C++的unordered_map,并处理可能的边界情况。比如,当数组中有负数时,前缀和可能会重复出现,所以哈希需要记录出现的次数,而不是仅仅存在与否。 最后,要确保代码示例的正确性,并验证是否覆盖了所有情况。例如,当k=0时,或者当子数组从第一个元素开始时,是否能够正确统计到。 总结下来,步骤应该是: 1. 创建哈希,初始化hash[0] =1。 2. 遍历数组,累加计算当前前缀和sum。 3. 检查哈希中是否存在sum -k,存在则将对应次数加到结果。 4. 将当前sum存入哈希,次数加1。 5. 遍历结束后返回结果。 这样处理可以确保所有可能的子数组都被统计到,时间复杂度为O(n),空间复杂度O(n)。 现在需要将这些步骤转化为中文解释,并附上正确的C++代码,同时生成相关问题。</think>### C++实现为K的连续子数组算法 #### 算法核心思路 使用**前缀和+哈希**优化时间复杂度,将暴力解法的$O(n^2)$降低到$O(n)$。核心公式为: $$pre[j] - pre[i] = k \quad \Rightarrow \quad pre[i] = pre[j] - k$$ 通过哈希记录前缀和出现次数,在遍历时直接查询满足条件的$pre[i]$值[^2][^3]。 #### 实现步骤 1. **初始化哈希**:`hash[0] = 1`,处理子数组从首元素开始的情况; 2. **遍历数组**:计算当前前缀和`sum`; 3. **查询哈希**:若存在`sum - k`的键,累加对应次数到结果; 4. **更新哈希**:记录当前前缀和出现次数; 5. **返回结果**:遍历结束后输出子数组个数。 #### C++代码实现 ```cpp #include <vector> #include <unordered_map> using namespace std; class Solution { public: int subarraySum(vector<int>& nums, int k) { unordered_map<int, int> hash; // 记录前缀和出现次数 hash[0] = 1; // 初始化:空数组为0的情况 int sum = 0, ret = 0; for (int x : nums) { sum += x; // 计算当前前缀和 if (hash.count(sum - k)) // 存在满足条件的pre[i] ret += hash[sum - k]; // 累加子数组个数 hash[sum]++; // 更新哈希 } return ret; } }; ``` #### 关键点解析 - **哈希预存**:`hash[0]=1`避免了漏判从数组开头到当前位置的子数组为$k$的情况; - **负数处理**:哈希记录所有前缀和出现次数,保证负数重复值场景的正确性; - **一次遍历**:计算前缀和与查询哈希的操作同步进行,时间复杂度$O(n)$。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值