LintCode 1293: Count of Range Sum

本文深入探讨了计数范围和问题,通过分析数组中满足特定条件的子数组数量,提出了有效的算法解决方案。利用前缀和与二叉搜索树的概念,优化了原始O(n^2)的时间复杂度,实现更高效的计算。

1293. Count of Range Sum

Given an integer array nums, return the number of range sums that lie in [lower, upper] inclusive.
Range sum S(i, j) is defined as the sum of the elements in nums between indices i and j (i ≤ j), inclusive.

Example

Example 1:

Input:[-2, 5, -1],-2,2
Output:3
Explanation:The three ranges are : [0, 0], [2, 2], [0, 2] and their respective sums are: -2, -1, 2.

Example 2:

Input:[0,-3,-3,1,1,2],3,5
Output:2
Explanation:The three ranges are : [3, 5], [4, 5] and their respective sums are: 4, 3.

Notice

A naive algorithm of O(n^2) is trivial. You MUST do better than that.

Input test data (one parameter per line)How to understand a testcase?

解法1:

参考的网上的思路。

首先得到presums,可用数组也可不用。

对于每个presums[i],我们要得到所有的nums[j]使得lower<=presums[i] - presums[j]<=upper,即

presums[i] >= presumes[j] + lower //我们要找到所有presums[i] - lower >= presums[j]的presums[j],当j小于一定值后所有的presums[j'都满足,所以取upper。
presums[i] <= presums[j] + upper //我们要找到所有presums[i] - upper <= presums[j]的presums[j],当j大于一定值后所有的presums[j]都满足,所以取lower。

 

class Solution {
public:
    /**
     * @param nums: a list of integers
     * @param lower: a integer
     * @param upper: a integer
     * @return: return a integer
     */
    int countRangeSum(vector<int> &nums, int lower, int upper) {
        int n = nums.size();
        multiset<long long> ms;
        long long presums = 0;
        int result = 0;
        ms.insert(0);

        for (int i = 0; i < n; ++i) {
            presums += nums[i];
            result += distance(ms.lower_bound(presums - upper), ms.upper_bound(presums - lower));
            ms.insert(presums);
        }
        
        return result;
    }
};

 

你的代码实现了“和为 `k` 的子数组个数”问题,使用了**前缀和(Prefix Sum)**的思想,但采用了两层循环来枚举所有子数组,时间复杂度为 $O(n^2)$。我们可以通过引入一个哈希表(字典)来记录前缀和的出现次数,将时间复杂度优化到 $O(n)$。 --- ### ❌ 你当前代码的问题: ```python for i in range(len(n_sum)-1): for j in range(i+1,len(n_sum)): if n_sum[j]-n_sum[i] == k: count +=1 ``` 这段是双重循环,导致整体复杂度为 $O(n^2)$。虽然能通过部分测试用例,但在大数据量时会超时。 --- ### ✅ 优化思路:使用哈希表存储前缀和频次 核心思想: - 我们不需要保存所有前缀和的值在列表中,只需要知道:**在当前位置之前,有多少个前缀和等于 `current_sum - k`**。 - 因为如果 `prefix[j] - prefix[i] = k`,那么 `prefix[i] = prefix[j] - k`。 - 所以我们可以边遍历边统计前缀和,并用哈希表记录每个前缀和出现的次数。 --- ### ✅ 优化后的 Python 代码($O(n)$ 时间复杂度): ```python from typing import List class Solution: def subarraySum(self, nums: List[int], k: int) -> int: count = 0 _sum = 0 prefix_count = {0: 1} # 初始化:前缀和为0出现1次(空子数组) for num in nums: _sum += num # 当前前缀和 # 检查是否存在前缀和为 _sum - k,若存在,则说明有子数组和为 k if _sum - k in prefix_count: count += prefix_count[_sum - k] # 更新当前前缀和的出现次数 prefix_count[_sum] = prefix_count.get(_sum, 0) + 1 return count ``` --- ### 🔍 代码解释: - `prefix_count = {0: 1}`:非常关键!表示前缀和为 0 出现了一次(处理从索引 0 开始就满足和为 `k` 的情况)。 - 遍历过程中: - 累加得到当前前缀和 `_sum`。 - 查询是否有前缀和为 `_sum - k`,如果有,说明从那个位置到当前位置的子数组和正好是 `k`。 - 将当前 `_sum` 记录进哈希表。 - 使用 `get(_sum, 0)` 安全地更新计数。 --- ### 📌 示例演示: ```python nums = [1, 1, 1], k = 2 ``` | i | num | _sum | _sum - k | 在哈希表中? | count | prefix_count | |---|-----|------|----------|--------------|-------|---------------| | 0 | 1 | 1 | -1 | 否 | 0 | {0:1, 1:1} | | 1 | 1 | 2 | 0 | 是 (1次) | 1 | {0:1, 1:1, 2:1} | | 2 | 1 | 3 | 1 | 是 (1次) | 2 | {0:1, 1:1, 2:1, 3:1} | 结果:`count = 2` → `[1,1]` 和 `[1,1]`(两个连续的) --- ### ⏱️ 复杂度分析: - **时间复杂度**:$O(n)$,只遍历一次数组。 - **空间复杂度**:$O(n)$,哈希表最多存 $n$ 个不同的前缀和。 --- ### 💡 为什么这个方法正确? 因为子数组 `nums[i:j]` 的和等于 `prefix[j] - prefix[i]`。我们要找这个差等于 `k` 的情况,即: $$ \text{prefix}[j] - \text{prefix}[i] = k \Rightarrow \text{prefix}[i] = \text{prefix}[j] - k $$ 所以我们在遍历到 `j` 时,只需查看前面有多少个 `prefix[i] == prefix[j] - k`。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值