这道题目是我之前做的RangeSum的一维形式的另一个变种。这次虽然空间上面是一维,但是其时间上进行了一个修改,导致问题的解决思路发生了改变,反倒回归了我刚一开始解题所想到的快速求某一个线段的长度的想法。
引入了一中新的,或者我还不熟悉的数据结构。
SegmentTree。线段树,或者叫做区间树。
实际上还是称为区间树更好理解一些。
树:是一棵树,而且是一棵二叉树。
线段:树上的每个节点对应于一个线段(还是叫“区间”更>容易理解,区间的起点和终点通常为整数)
同一层的节点所代表的区间,相互不会重叠。
叶子节点的区间是单位长度,不能再分了。线段树是一棵二叉树,树中的每一个结点表示了一个区间[a,b]。a,b通常是整数。每一个叶子节点表示了一个单位区间。对于每一个非叶结点所表示的结点[a,b],其左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2,b](除法去尾取整)。
线段树的基本用途:
线段树适用于和区间统计有关的问题。比如某些数据可以按区间进行划分,按区间动态进行修改,而且还需要按区间多次进行查询,那么使用线段树可以达到较快查询速度。线段树的构建
createSeg //以节点v为根建树、v对应区间为[l,r]
{
对节点v初始化
if (l!=r)
{
以v的左孩子为根建树、区间为[l,(l+r)/2]
以v的右孩子为根建树、区间为[(l+r)/2+1,r]
}
}
这道题我使用原来的方法并没有满足时间复杂度的要求,根据讨论区整理出来的答案。
下面是leetcode上讨论区某网友的代码。
public class NumArray {
class SegmentTreeNode {
int start, end;
SegmentTreeNode left, right;
int sum;
public SegmentTreeNode(int start, int end) {
this.start = start;
this.end = end;
this.left = null;
this.right = null;
this.sum = 0;
}
}//定义数据结构,和学习的二叉树基本相似。看来树是很有用的呢。
SegmentTreeNode root = null;//成员变量,树的根节点
public NumArray(int[] nums) {
root = buildTree(nums, 0, nums.length-1);//核心方法
}
private SegmentTreeNode buildTree(int[] nums, int start, int end) {
if (start > end) {//排除特殊情况,返回空指针
return null;
} else {
SegmentTreeNode ret = new SegmentTreeNode(start, end);//建立要返回的树的第一个节点
if (start == end) {//通过二分的约去导致start和end指向了同一个节点,即为叶子节点。没有左右孩子,其sum值也即为数组存储的值nums[start]。
ret.sum = nums[start];
} else {
int mid = start + (end - start) / 2;//平凡而重要的分树语句,决定了树的分差和叶子节点的出现。
ret.left = buildTree(nums, start, mid);
ret.right = buildTree(nums, mid + 1, end);
ret.sum = ret.left.sum + ret.right.sum;
}
return ret;
}
}
void update(int i, int val) {
update(root, i, val);
}
void update(SegmentTreeNode root, int pos, int val) {
if (root.start == root.end) {//边缘情况处理,不能忘记,往往就是树的叶子情况。
root.sum = val;
} else {
int mid = root.start + (root.end - root.start) / 2;
if (pos <= mid) {
update(root.left, pos, val);
} else {
update(root.right, pos, val);
}
root.sum = root.left.sum + root.right.sum;//本主干的sum的值同样要获得更新。
}
}
public int sumRange(int i, int j) {
return sumRange(root, i, j);
}
public int sumRange(SegmentTreeNode root, int start, int end) {
if (root.end == end && root.start == start) {//如果查询的恰好是本节点主管的范围,直接返回
return root.sum;
} else {//不是的话就求中间点,划分范围
int mid = root.start + (root.end - root.start) / 2;
if (end <= mid) {//整体偏向左侧
return sumRange(root.left, start, end);
} else if (start >= mid+1) {//整体偏向右侧
return sumRange(root.right, start, end);
} else { //横跨在中心点两侧,就带入左右子树中求取两遍之和作为最终结果。
return sumRange(root.right, mid+1, end) + sumRange(root.left, start, mid);
}
}
}
我重写的代码,经过了一次一次debug,照着别人的 一行一行对还看了两遍才看完。不能有一点语意的不同,这就是程序
public class NumArray {
private class Node{//tree node
Node left,right;//left and right child
int start,end; //the range charged by the node
int sum; //sum of the range charged by the node
public Node(){
left=right=null;
sum=0;
}
}
Node root=null; //root of the the tree for this problem
public NumArray(int[] nums) {//in this function, we shoule create a tree to hold all elements in nums and calc sum.which achieved by recursion.
int length=nums.length;
root=makeATree(nums,0,length-1);
}
private Node makeATree(int[] nums,int start,int end){
if(start>end){return null;}
Node tree=new Node();
tree.start=start;
tree.end=end;
if(start==end){
tree.sum=nums[start];
}
else { //if this is the leaf node
int mid=start+(end-start)/2;
tree.left=makeATree(nums,start,mid);
tree.right=makeATree(nums,mid+1,end);
tree.sum=tree.left.sum+tree.right.sum;
}
return tree;
}
void update(int i, int val) {
myUp(i,val,root);
}
private void myUp(int i,int val,Node tree){
if(tree.start==tree.end){
tree.sum=val;
}
else{
int mid=tree.start+(tree.end-tree.start)/2;
if(i<=mid){
myUp(i,val,tree.left);
}else {
myUp(i,val,tree.right);
}
tree.sum=tree.left.sum+tree.right.sum;
}
}
public int sumRange(int i, int j) {
return calRange(i,j,root);
}
private int calRange(int start,int end,Node tree){
if(start==tree.start&&end==tree.end){//edge case and leaf node case
return tree.sum;
}else{ //if not the leaf node
int mid =tree.start+(tree.end-tree.start)/2;//错误点 tree.start tree.end ,是按照树的规则查找的,传入的start end进行取中没有意义。
if(end<=mid){ //in total left
return calRange(start,end,tree.left);//错误点
}else if(start>=mid+1){ //错误点,这里要是mid+1 //in total right
return calRange(start,end,tree.right);
}else{ //in part of the node range involve mid
return calRange(start,mid,tree.left)+calRange(mid+1,end,tree.right);
}
}
}
}
总结
引入新的数据结构难免有些不适应,带着迷糊糊的数据结构的概念去编码难免会产生错误。可以用纸张画一画草图,看看其内部的运作的机制。