Leetcode307
m
e
d
i
u
m
\color{#FF4500}{medium}
medium
点击进入原题链接:单词搜索II Word SearchII
Given an integer array nums, find the sum of the elements between indices i and j (i ≤ j), inclusive.
The update(i, val) function modifies nums by updating the element at index i to val.
Constraints:
- The array is only modifiable by the update function.
- You may assume the number of calls to update and sumRange function is distributed evenly.
- 0 <= i <= j <= nums.length - 1
思路一 暴力解法
用一个数组sum
记录前i
个元素的和,那么区间[i,j]
和就是sum[j]-sum[i]
,在创建数组的时候需要遍历一遍整个数组,复杂度O(n);修改元素i
的时候要修改sum[i]
以及之后的所有元素,复杂度为O(n);求区间和直接拿两项sum[j]-sum[i]
相减,时间复杂度为O(1)。但由于题目说修改和求和的数量均等,那么总体时间复杂度为O(n)。
空间复杂度: 额外用了一个数组记录前i
项和,因此是O(n)。
class NumArray {
public:
NumArray(vector<int>& nums) :_nums(nums) {
if(nums.empty()) return;
//initialize O(n)
sum.push_back(nums[0]);
for (int i = 1; i < nums.size(); i++) {
sum.push_back(sum[i - 1] + nums[i]);
}
}
void update(int i, int val) {
//
for(int j=i;j<sum.size();j++){
sum[j] -=(_nums[i]-val);
}
_nums[i] = val;
}
int sumRange(int i, int j) {
if(i==0) return sum[j];
return sum[j]-sum[i-1];
}
private:
vector<int> sum; //前i项和
vector<int>& _nums;
};
这里有个需要注意的地方,vector<int>& _nums;
是一个引用,他作为类成员变量,必须要在构造函数的初始化列表里赋值: NumArray(vector<int>& nums) :_nums(nums) {...}
思路二 线段树
线段树是一种非常灵活的数据结构,它可以用于解决多种范围查询问题,比如在对数时间内从数组中找到最小值、最大值、总和、最大公约数、最小公倍数等。
这里可以将数组空间进行二分,左孩子为左半边数组元素的和,右孩子为右半边数组元素的和。
这里我是用递归法实现的,数组中间会存在大量空的0(比如上图中的下表9,10,13,14…都是空节点)。为什么呢?因为在产生新的左孩子和右孩子的时候,默认前面的堂兄弟节点都是存在的。以上图中下标11的节点为例,下表11直接由节点5产生(5*2+1)
,而没有考虑节点9和节点10是空的(因为下标4已经是叶节点了),因此会产生一些空节点。
最坏的情况下,需要给数组预留的空间是满二叉树的情况。如果有N个数字,一般来说预留4*N的空间是足够的。
class NumArray {
public:
NumArray(vector<int>& nums) :_nums(nums) {
if (nums.empty()) return;
//initialize O(n)
SegmentTree = vector<int>(4 * nums.size(), 0);//一般线段树数组的大小是线性数组的4倍
//build a segment tree
SegmentTree_builder(0, 0, nums.size() - 1, SegmentTree, nums);
}
void update(int i, int val) {
int d = val - _nums[i];
_nums[i] = val;
SegmentTree_update(i, d, 0, 0, _nums.size() - 1, SegmentTree);
}
int sumRange(int i, int j) {
return SegmentTree_search(i, j, 0, 0, _nums.size() - 1, SegmentTree);
}
private:
void SegmentTree_builder(int pos, int left, int right, vector<int>& SegmentTree, vector<int>& nums) {
if (left == right) {
SegmentTree[pos] = nums[left];
return;
}
int mid = (left + right) / 2;
//左子树
SegmentTree_builder(2 * pos + 1, left, mid, SegmentTree, nums);
//右子树
SegmentTree_builder(2 * pos + 2, mid + 1, right, SegmentTree, nums);
//本节点等于左右节点之和
SegmentTree[pos] = SegmentTree[2 * pos + 1] + SegmentTree[2 * pos + 2];
}
void SegmentTree_update(const int& i, const int& d, int pos, int left, int right, vector<int>& SegmentTree)
{
SegmentTree[pos] += d; //更新当前节点
int mid = (left + right) / 2;
if (i == left && i==right) return;
if (i <= mid) SegmentTree_update(i, d, 2 * pos + 1, left, mid, SegmentTree);
else SegmentTree_update(i, d, 2 * pos + 2, mid + 1, right, SegmentTree);
}
int SegmentTree_search(const int& i, const int&j, int pos, int left, int right, vector<int>& SegmentTree)
{
if (i > right || j < left) return 0; //无交集
if (i <= left && j >= right) return SegmentTree[pos]; //查找的空间被要求的空间所包含
int mid = (left + right) / 2;
int left_val = SegmentTree_search(i, j, 2 * pos + 1, left, mid, SegmentTree);
int right_val = SegmentTree_search(i, j, 2 * pos + 2, mid + 1, right, SegmentTree);
return left_val + right_val;
}
vector<int> SegmentTree;
vector<int>& _nums;
};