今天做到一个题目,看起来很简单很简单,但是O(n)的复杂度都超时了
题目如下
正确的思路是利用线段树。加入给我们一个长度为n的数组,我们可以将n个元素的和均分到一颗二叉树上,假设二叉树的节点层序遍历为0,1,2,3,4…。我们可以用一个数组来存储这颗树。根节点0保存0到n-1内的元素值之和。m=0+(0+n-1)/2。0的左节点为1保存0到m内的元素值之和,0的右节点2保存m+1到n-1内元素值之和。再分别对1和2之两个节点划分。直到最后节点保存的只有一个元素为止。
代码如下
class NumArray {
private:
int n;
vector<int> tree;
protected:
void build(int node, int s, int e, vector<int> &nums){
if(s == e){
tree[node] = nums[s];
return;
}
int m = (s + e) / 2;
int left_node = 2 * node + 1;
int right_node = 2 * node + 2;
build(left_node, s, m, nums);
build(right_node, m + 1, e, nums);
tree[node] = tree[left_node] + tree[right_node];
}
void change(int node, int s, int e, int val, int index){
if(s == e){
tree[node] = val;
return;
}
int m = (s + e) / 2;
int left_node = 2 * node + 1;
int right_node = 2 * node + 2;
if(m >= index){
change(left_node, s, m, val, index);
}else{
change(right_node, m+1, e, val, index);
}
tree[node] = tree[left_node] + tree[right_node];
}
int sum(int node, int s, int e, int left, int right){
if(left == s && right == e){
return tree[node];
}
int m = (s + e) / 2;
int left_node = 2 * node + 1;
int right_node = 2 * node + 2;
if(m < left){
return sum(right_node, m + 1, e, left, right);
}else if(m >= right){
return sum(left_node, s, m, left, right);
}else{
return sum(right_node, m + 1, e, m + 1, right) + sum(left_node, s, m, left, m);
}
}
public:
NumArray(vector<int>& nums) : n(nums.size()){
tree.resize(n * 4);
//构建一棵树
build(0, 0, n - 1, nums);
}
void update(int index, int val) {
change(0, 0, n - 1, val, index);
}
int sumRange(int left, int right) {
return sum(0, 0, n - 1, left, right);
}
};
时间复杂度的分析
而且由于我们将所有的元素分布再一棵二叉树的叶子节点上。所以深度为log(n)。
在我们构造树的时候,要遍历整棵树,所以时间复杂度为O(n),但是在我们改变一个元素的值时。我们在照这个元素的过程中,我们的时间复杂度只有O(log(n))。而求和的时候因为我们的树的节点是便是一个索引范围内元素值之和。所以只要我们将区间分割的对应上了。其平均时间复杂度是KO(log(n)),K为一个常树,最坏最坏不会超过O(n*log(n))。
我觉得这个代码里面构造树和更新节点值以及求范围元素和的方法还是可圈可点的。提笔记之吧。