题目描述
难度:中等
实现思路
- 暴力枚举(超时)
- 分块处理
- 线段树
算法实现
初步实现(暴力枚举超时)
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秒_]
网上的一种方法:线段树。
这题还能用树结构来处理吗(看上去还真可以),二叉树🌲能理解,这线段树又是什么?怎么处理的这个题目?
================