Leetcode(303)——区域和检索 - 数组不可变

Leetcode(303)——区域和检索 - 数组不可变

题目

给定一个整数数组 nums,处理以下类型的多个查询:

计算索引 left 和 right (包含 left 和 right)之间的 nums 元素的 ,其中 left <= right
实现 NumArray 类:

  • NumArray(int[] nums) 使用数组 nums 初始化对象
  • int sumRange(int i, int j) 返回数组 nums 中索引 left 和 right 之间的元素的 总和 ,包含 left 和 right 两点(也就是 nums[left] + nums[left + 1] + … + nums[right] )

示例 1:

输入:
[“NumArray”, “sumRange”, “sumRange”, “sumRange”]
[[[-2, 0, 3, -5, 2, -1]], [0, 2], [2, 5], [0, 5]]
输出:
[null, 1, -1, -3]
解释:
NumArray numArray = new NumArray([-2, 0, 3, -5, 2, -1]);
numArray.sumRange(0, 2); // return 1 ((-2) + 0 + 3)
numArray.sumRange(2, 5); // return -1 (3 + (-5) + 2 + (-1))
numArray.sumRange(0, 5); // return -3 ((-2) + 0 + 3 + (-5) + 2 + (-1))

提示:

  • 1 <= nums.length <= 1 0 4 10^4 104
  • − 1 0 5 -10^5 105 <= nums[i] <= 1 0 5 10^5 105
  • 0 <= i <= j < nums.length
  • 最多调用 1 0 4 10^4 104 次 sumRange 方法

题解

方法一:暴力枚举

思路

​​  1.

代码实现

复杂度分析

时间复杂度: O ( ) O() O() ,其中 $$ 是。
空间复杂度: O ( ) O() O()

方法二:保存从任意下标 i 到另一下标 r 的所有结果

思路

​​  在这里

代码实现
class NumArray {
private:
	// vector 也可以,不一定要 unordered_map 
    unordered_map<int, unordered_map<int, int>> lr_value;   // 保存l到r的结果
public:
    NumArray(vector<int>& nums){
        // 初始化 NumArray 时就计算好所有的结果,对应存给lr_value[i][j]
        int curr;
        int ans = 0;
        for(int a = 0; a < nums.size(); a++){
            for(int b = a; b < nums.size(); b++){
                curr = a;
                while(curr <= b){
                    ans += nums[curr++];
                }
                lr_value[a][b] = ans;
                ans = 0;    // 重置ans
            }
        }
    }
    
    int sumRange(int left, int right) {        
        return lr_value[left][right];
    }
};
复杂度分析

时间复杂度: O ( N 3 ) O(N^3) O(N3) ,其中 N N N 是数组 nums 中的元素个数。因为最外面两层循环的时间复杂度为 O ( N 2 ) O(N^2) O(N2) ,而内层循环的时间复杂度为 O ( N ) O(N) O(N),所以总时间复杂度为 O ( N 3 ) O(N^3) O(N3)
空间复杂度: O ( N 2 ) O(N^2) O(N2) ,其中 N N N 是数组 nums 中的元素个数。

方法三:采用 DP 思想优化保存过程

思路

​​  我们在这里采用 DP 思想对方法二进行一部分优化:在内循环中不再重复求之前的结果,而是直接将其拿来与当前值相加即可获得所要值。

代码实现
class NumArray {
private:
	// vector 也可以,不一定要 unordered_map 
    unordered_map<int, unordered_map<int, int>> lr_value;   // 保存l到r的结果
public:
    NumArray(vector<int>& nums){
        // 初始化 NumArray 时就计算好所有的结果,对应存给lr_value[i][j]
        for(int a = 0; a < nums.size(); a++){
            for(int b = a; b < nums.size(); b++){
                // 采用DP思想,将之前的结果拿来使用
                if(a == b)
                    lr_value[a][b] = nums[a];
                else lr_value[a][b] = lr_value[a][b-1] + nums[b];
            }
        }
    }
    
    int sumRange(int left, int right) {        
        return lr_value[left][right];
    }
};
复杂度分析

时间复杂度: O ( N 2 ) O(N^2) O(N2) ,其中 N N N 是数组 nums 中的元素个数。因为最外面两层循环的时间复杂度为 O ( N 2 ) O(N^2) O(N2) ,而内层循环的时间复杂度为 O ( 1 ) O(1) O(1),所以总时间复杂度为 O ( N 2 ) O(N^2) O(N2)
空间复杂度: O ( N 2 ) O(N^2) O(N2) ,其中 N N N 是数组 nums 中的元素个数。

方法四:前缀和

思路

​​  在这里我们学习一个新算法——前缀和():前缀和是指某序列的前 n 项和,可以把它理解为数学上的数列的前 n 项和,而差分可以看成前缀和的逆运算。合理的使用前缀和与差分,可以将某些复杂的问题简单化。

​​  nums 数组的每一项都对应有它的前缀和: nums 的第 0 项到 当前项 的和。用数组 preSum 表示,则 p r e S u m [ i ] preSum[i] preSum[i] 表示:第 0 项到 第 i 项 的和:
p r e S u m [ i ] = n u m s [ 0 ] + n u m s [ 1 ] + … + n u m s [ i ] preSum[i] = nums[0] + nums[1] +…+nums[i] preSum[i]=nums[0]+nums[1]++nums[i]

我们可以很容易知道,nums 的某项 = 两个相邻前缀和的差:
n u m s [ i ] = p r e S u m [ i ] − p r e S u m [ i − 1 ] nums[i] = preSum[i] - preSum[i - 1] nums[i]=preSum[i]preSum[i1]

对于 nums 的 i 到 j 的元素和,上式叠加,有:
n u m s [ i ] + … + n u m s [ j ] = p r e S u m [ j ] − p r e S u m [ i − 1 ] nums[i] +…+nums[j]=preSum[j] - preSum[i - 1] nums[i]++nums[j]=preSum[j]preSum[i1]

当 i 为 0 时,此时 i-1 为 -1,我们故意让preSum[-1]为 0,使得上式在i=0时也成立:
n u m s [ 0 ] + … + n u m s [ j ] = p r e S u m [ j ] nums[0] +…+nums[j]=preSum[j] nums[0]++nums[j]=preSum[j]

所以: s u m R a n g e ( i , j ) = p r e S u m [ j ] − p r e S u m [ i − 1 ] sumRange(i, j)=preSum[j] - preSum[i - 1] sumRange(i,j)=preSum[j]preSum[i1]
我们在初始化阶段求出 preSum 数组的每一项,求 sumRange(i,j) 时直接返回 preSum[j] - preSum[i-1]

怎么求 preSum 数组
​​  利用前面提到的递推式:
p r e S u m [ i ] = p r e S u m [ i − 1 ] + n u m s [ i ] preSum[i] = preSum[i - 1]+nums[i] preSum[i]=preSum[i1]+nums[i]

​​  遍历求出每一个 p r e S u m [ i ] preSum[i] preSum[i],别忘了预置 p r e S u m [ − 1 ] = 0 preSum[-1] = 0 preSum[1]=0,即 p r e S u m [ 0 ] = n u m s [ 0 ] + p r e S u m [ − 1 ] preSum[0] = nums[0] + preSum[-1] preSum[0]=nums[0]+preSum[1] (前提是 nums 有元素)。
​​  预置 p r e S u m [ − 1 ] preSum[-1] preSum[1] 这种情况,只是为了边界情况也能套用通式。

代码实现
class NumArray {
private:
    vector<int> Presum;
public:
    NumArray(vector<int>& nums) {
        int n = nums.size();
        Presum.resize(n+1);
        for(int i = 1; i < n+1; i++)
        {
            Presum[i] = Presum[i-1] + nums[i-1];
        }
    }
    
    int sumRange(int left, int right) {
        return Presum[right+1] - Presum[left];
    }
};
复杂度分析

时间复杂度:初始化时的时间复杂度 O ( N ) O(N) O(N) ,其中 N N N 是数组 nums 的元素个数。而获取结果,即调用函数 sumRange 的时间复杂度为 O ( 1 ) O(1) O(1)
空间复杂度: O ( N ) O(N) O(N)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值