Range Sum Query - Mutable -leetcode

这道题目是我之前做的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);
           }
       }
    }
}

总结

引入新的数据结构难免有些不适应,带着迷糊糊的数据结构的概念去编码难免会产生错误。可以用纸张画一画草图,看看其内部的运作的机制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小马工匠坊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值