给你一个数组
nums
,请你完成两类查询。
- 其中一类查询要求 更新 数组
nums
下标对应的值- 另一类查询要求返回数组
nums
中索引left
和索引right
之间( 包含 )的nums元素的 和 ,其中left <= right
实现
NumArray
类:
NumArray(int[] nums)
用整数数组nums
初始化对象void update(int index, int val)
将nums[index]
的值 更新 为val
int sumRange(int left, int right)
返回数组nums
中索引left
和索引right
之间( 包含 )的nums元素的 和 (即,nums[left] + nums[left + 1], ..., nums[right]
)示例 1:
输入: ["NumArray", "sumRange", "update", "sumRange"] [[[1, 3, 5]], [0, 2], [1, 2], [0, 2]] 输出: [null, 9, null, 8] 解释: NumArray numArray = new NumArray([1, 3, 5]); numArray.sumRange(0, 2); // 返回 1 + 3 + 5 = 9 numArray.update(1, 2); // nums = [1,2,5] numArray.sumRange(0, 2); // 返回 1 + 2 + 5 = 8提示:
1 <= nums.length <= 3 * 104
-100 <= nums[i] <= 100
0 <= index < nums.length
-100 <= val <= 100
0 <= left <= right < nums.length
- 调用
update
和sumRange
方法次数不大于3 * 104
解题思路
拿到题目,暴力解决:
class NumArray {
public:
NumArray(vector<int>& nums) {
arr = vector<int>(nums.size()+1);
for (int i = 0; i < nums.size(); i++) {
arr[i] = nums[i];
}
}
void update(int index, int val) {
arr[index] = val;
}
int sumRange(int left, int right) {
int sum = 0;
for (int i = left; i <= right; i++)
sum += sum_arr[i];
return sum;
}
private:
vector<int> arr;
};
超时。。。
分析问题,减少计算时间,分块思想;
1、分块求解能降低时间:
class NumArray {
private:
vector<int> sum; // sum[i] 表示第 i 个块的元素和
int size; // 块的大小
vector<int> &nums;
public:
NumArray(vector<int>& nums) : nums(nums) {
int n = nums.size();
size = sqrt(n);
sum.resize((n + size - 1) / size); // n/size 向上取整
for (int i = 0; i < n; i++) {
sum[i / size] += nums[i];
}
}
void update(int index, int val) {
sum[index / size] += val - nums[index];
nums[index] = val;
}
int sumRange(int left, int right) {
int b1 = left / size, i1 = left % size, b2 = right / size, i2 = right % size;
if (b1 == b2) { // 区间 [left, right] 在同一块中
return accumulate(nums.begin() + b1 * size + i1, nums.begin() + b1 * size + i2 + 1, 0);
}
int sum1 = accumulate(nums.begin() + b1 * size + i1, nums.begin() + b1 * size + size, 0);
int sum2 = accumulate(nums.begin() + b2 * size, nums.begin() + b2 * size + i2 + 1, 0);
int sum3 = accumulate(sum.begin() + b1 + 1, sum.begin() + b2, 0);
return sum1 + sum2 + sum3;
}
};
2. 树状数组
一个长度为n的数组,完成单点修改或区间求和:
时间复杂度:
朴素算法:单点修改:O(1)、区间查询:O(n)
树状数组:单点修改:O(logn)、区间查询: O(logn)
分区思想:
所有层的第偶数个数字都是没有用的,即使去掉也不影响计算。去掉所有第偶数个数字,若数组长度为偶数则剩下数据组成的数组长度和原数组长度一样,剩下数据组成的数组为树状数组。
区间求和:只需要求区间包含的子区间的和即可。
单点修改:只需要向上修改被包含的区间即。
树状数组:
t[x]节点的父节点为t[x+lowbit(x)],t[x]节点长度等于lowbit(x)、t[x]父节点长度等于t[x+lowbit(x)]
lowbit计算:
1、消掉最后一个1,再用原值减去该值
int lowbit(x)
{
return x - (x & (x - 1));
}
2、正数原码和补码相同,负数求补码:把原码的符号位保持不变,数值位逐位取反,即可得原码的反码,在反码的基础上加 1 即得该原码的补码。
int lowbit(x)
{
return x & -x;
}
更新后缀和,构建和修改
void update(int x, int val, ArrayInt c, int n)
{
for ( ; x <= n; c[x] += val, x += lowbit(x));
}
查询前缀和,求和
int sum(int x, ArrayInt c, int n)
{
int ret = 0;
for ( ; x > 0; ret += c[x], x -= lowbit(x));
return ret;
}
解题:
class NumArray {
private:
vector<int> tree; // 树状树组
vector<int> nums;
int lowbit(int x)
{
return (x & -x);
}
void add(int index, int val) {
for(;index <=nums.size(); index += lowbit(index))
tree[index] += val;
}
public:
void update(int index, int val) {
int idx = index + 1;
for(;idx <=nums.size(); idx += lowbit(idx)) {
tree[idx] += (val - nums[index]);
}
nums[index] = val;
}
NumArray(vector<int>& nums) : nums(nums) {
tree.resize(nums.size()+1);
for (int i = 1; i <= nums.size(); i++) {
add(i,nums[i-1]);
}
}
int sumRange(int left, int right) {
int sum1 = 0,sum2 = 0;
for(;left > 0 ; left -= lowbit(left))
sum1 += tree[left];
int r = right + 1;
for(; r > 0; r -= lowbit(r))
sum2 += tree[r];
return (sum2 - sum1);
}
};
3、线段树
线段树也是通过分区来计算来减少计算;
时间复杂度:
朴素算法:单点修改:O(1)、区间查询:O(n)
线段树:单点修改:O(logn)、区间查询: O(logn)
数组:
线段树构建过程
线段树数组表示:
可以发现:节点k左孩子序号为: 2k+1; 右孩子序号为:2k+2;节点k为左孩子和右孩子的并集;
线段树构建:
void build_tree(int k, int start, int end)
{
if (start == end) {
tree[k] = nums[start];
} else {
int mid = (start + end) / 2;
build_tree(2*k +1, start, mid);
build_tree(2*k +2, mid+1, end);
tree[k] = tree[2*k+1] + tree[2*k +2];
}
}
线段树更新:
void update_tree(int k, int start, int end, int index, int val) {
if (end == start) {
nums[index] = val;
tree[k] = val;
} else{
int mid = (start + end) / 2;
if (index <= mid && index >= start) {
update_tree(2*k+1, start,mid,index,val);
} else {
update_tree(2*k+2, mid+1, end, index, val);
}
tree[k] = tree[2*k +1] + tree[2*k +2];
}
}
线段树查询:
int query_tree(int k, int start, int end, int L, int R) {
if (L > end || R < start)
return 0;
else if(start == end) {
return tree[k];
} else if (L <= start && R>= end ) {
return tree[k];
} else {
int mid = (start + end) / 2;
int sum_left = query_tree(2*k+1,start,mid,L,R);
int sum_right = query_tree(2*k+2,mid+1,end,L,R);
return (sum_left + sum_right);
}
}
解题:
class NumArray {
private:
vector<int> &nums;
vector<int> tree;
void build_tree(int k, int start, int end)
{
if (start == end) {
tree[k] = nums[start];
} else {
int mid = (start + end) / 2;
build_tree(2*k +1, start, mid);
build_tree(2*k +2, mid+1, end);
tree[k] = tree[2*k+1] + tree[2*k +2];
}
}
void update_tree(int k, int start, int end, int index, int val) {
if (end == start) {
nums[index] = val;
tree[k] = val;
} else{
int mid = (start + end) / 2;
if (index <= mid && index >= start) {
update_tree(2*k+1, start,mid,index,val);
} else {
update_tree(2*k+2, mid+1, end, index, val);
}
tree[k] = tree[2*k +1] + tree[2*k +2];
}
}
int query_tree(int k, int start, int end, int L, int R) {
if (L > end || R < start)
return 0;
else if(start == end) {
return tree[k];
} else if (L <= start && R>= end ) {
return tree[k];
} else {
int mid = (start + end) / 2;
int sum_left = query_tree(2*k+1,start,mid,L,R);
int sum_right = query_tree(2*k+2,mid+1,end,L,R);
return (sum_left + sum_right);
}
}
public:
NumArray(vector<int>& nums):nums(nums) {
tree.resize(nums.size()*4);
build_tree(0,0,nums.size()-1);
}
void update(int index, int val) {
update_tree(0,0,nums.size()-1,index,val);
}
int sumRange(int left, int right) {
return query_tree(0,0,nums.size()-1,left,right);
}
};