[LeetCode]307. 区域和检索 -数组可修改(可再优化)


题目描述

难度:中等
在这里插入图片描述

实现思路

  • 暴力枚举(超时)
  • 分块处理
  • 线段树

算法实现

初步实现(暴力枚举超时)

class NumArray {

    int[] array;
    public NumArray(int[] nums) {
        array = nums;
    }
    
    public void update(int index, int val) {
        array[index] = val;
    }
    
    public int sumRange(int left, int right) {
        int sum=0;
        for(int i=left;i<=right;++i){
            sum+=array[i];
        }
        return sum;
    }
}

/**
 * Your NumArray object will be instantiated and called as such:
 * NumArray obj = new NumArray(nums);
 * obj.update(index,val);
 * int param_2 = obj.sumRange(left,right);
 
 [2022年04月05日13时48分59秒_]
 这个题看着简单,第一遍做错了,最后一个用例(第15个用例)超时
 ================
 [2022年04月05日13时57分03秒_]
 超出时间了,这怎么优化?时间复杂度高了?
 ================
 */
 

在这里插入图片描述
在这里插入图片描述
[2022年04月05日14时13分27秒_]
最后一个测试用例是什么?怎么超时了?第14个用例也超时了…?在这里插入图片描述

================
[2022年04月05日14时24分29秒_]
遇到知识盲区了,肯定不是这么做的。
看下题解了,有三个方法:分块处理、线段树、树状数组,这都是啥😳,又遇到知识盲区了。

================
[2022年04月05日14时31分29秒_]
只要nums的数据长度足够大,调用update和sumRange方法次数足够多,这种笨拙的暴力枚举就一定会超时。

================
[2022年04月05日14时52分04秒_]
知识盲点、知识盲区,算法盲点、算法盲区。待进一步研究了。

================

[2022年04月05日16时51分58秒_]
再想想,有什么办法可以减少时间复杂度,不要超时。只要能提高算法时间上的效率,增加一点空间,也是可以的吧?

从题意的上限来做测试和优化:
按数据长度最大3万个,函数操作次数3万次,来优化。做到不超时100ms,有什么思路吗?

================

[2022年04月05日17时19分03秒_]
在暴力枚举的做法中,耗时的地方在哪儿:
1.update耗时吗?
分析:update不耗时,O(1)时间复杂度
2.sumRange耗时吗?
分析:O(n)的时间复杂度,这个比较耗时,这个可以优化一下。能不能做到求和过程中减少遍历的次数?

================
[2022年04月05日18时20分25秒_]
假如sumRange求和下标区域为0_29999,那么一次区域求和函数要做30000次累加操作,很显然这是很耗时的。调用n次时间复杂度就是n的n次方了。
能不能将其分成x块(分成一个个区域,并保存每个区域的和值),这样时间复杂度便是n/x。
在这里插入图片描述

================

分块处理

在这里插入图片描述
在这里插入图片描述
思路:在对暴力枚举的分析基础上做了优化,使用自己理解的分块处理,做了分块处理的编码实现,以10个为一小分块,理论提升了10倍的速度。

class NumArray {

    int[] array;//原数组
    int[] area;//分块数组
    int splitSpec = 10;//分块大小规格,默认10个为一个小分块
    
    public NumArray(int[] nums) {
        array = nums;
        //初始化分块数组长度
        area = new int[nums.length/splitSpec+1];
        
        //初始化按小分块求和分别保存
        int aIndex = 0;
        int areaSum = 0;
        for(int i = 0;i<nums.length;++i){
            areaSum+=nums[i];
            if((i+1) % splitSpec==0){
                area[aIndex]=areaSum;
                areaSum = 0;
                ++aIndex;
            }else{
                area[aIndex]=areaSum;
            }
        }
    }
    
    public void update(int index, int val) {
        //所在小分块下标定位
        int uIndex =index/splitSpec;   
        
        //更新原数组
        array[index] = val;
        
        //计算当前小分块首尾下标
        //首
        int begin = uIndex*splitSpec;
        //尾
        int tmp = (uIndex+1)*splitSpec-1;
        int end = 0;
        if(tmp>array.length-1){
            end =array.length-1;
        }else{
            end =tmp;
        }
        
        //更新当前小分块和
        area[uIndex]=0;
        for(int i=begin;i<=end;++i){
            area[uIndex] +=array[i];
        }
        
    }
    
    public int sumRange(int left, int right) {
        int sum=0;
        
        //覆盖的分块下标定位
        int areaLeftIndex = left/splitSpec;
        int areaRightIndex = right/splitSpec;
        
        //累加覆盖的分块和值
        for(int i=areaLeftIndex;i<=areaRightIndex;++i){
            sum+=area[i];
        }
        
        //确定残余块左右下标边界
        int leftStart = areaLeftIndex*splitSpec;
        int rightEnd = 0;
        int tmp=(areaRightIndex+1)*splitSpec-1;
        if(tmp>=array.length-1){
            rightEnd = array.length-1;
        }else{
            rightEnd = tmp;
        }
        
        //左右残余块求和
        int deLeft = 0;
        int deRight = 0;
        if(leftStart<left){
            for(int i=leftStart;i<left;++i){
                deLeft+=array[i];
            }
        }
        if(rightEnd <=array.length-1){
            for(int e=right+1;e<=rightEnd;++e){
                deRight+=array[e];
            }
        }
        
        //返回差值
        return sum-deLeft-deRight;
    }
}

/**
 * Your NumArray object will be instantiated and called as such:
 * NumArray obj = new NumArray(nums);
 * obj.update(index,val);
 * int param_2 = obj.sumRange(left,right);
 
 [2022年04月05日21时24分02秒_]
 分块处理,还有一些要处理,赶脚越写越复杂,不写了先,我思路没挼清楚
================

 [2022年04月05日23时20分36秒_]
ok,过啦~( ̄▽ ̄~)~,分块处理好
================
 */
 

在这里插入图片描述
这个题目的测试案例,以80为单位的小分块,效率最佳:在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
[2022年04月06日21时34分11秒_]
可以再做一些优化吗,将耗时控制在50ms内?或者将耗时控制在50-85ms内?
想想,再想想…

================
[2022年04月06日22时32分34秒_]
想不出来更优的算法了(๑>ڡ<)☆

================

线段树

[2022年04月06日22时31分46秒_]
网上的一种方法:线段树。
这题还能用树结构来处理吗(看上去还真可以),二叉树🌲能理解,这线段树又是什么?怎么处理的这个题目?

================在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dnbug Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值