Leetcode 307. Range Sum Query - Mutable

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.

Example:

Given nums = [1, 3, 5]

sumRange(0, 2) -> 9
update(1, 2)
sumRange(0, 2) -> 8

 

Note:

  1. The array is only modifiable by the update function.
  2. You may assume the number of calls to update and sumRange function is distributed evenly.

之前没有接触过,上来就暴力算法,结果是AC不了的。

解决方案

方案1,每次求和,直接遍历子数组进行求和。每次更新,直接根据下标更新元素值。求和操作时间复杂度为 O(n), 更新操作时间复杂度为O(1)。--> 不能AC

方案2,采用线段树存储原数组以及中间结果。

方案3, 采用树状数组的方案(待更。。)

         则用   线段树  解法 或者  树状数组

首先了解一下线段树的概念(之前没有接触过):线段树的本质是一颗二叉树,每一个节点代表着一个区间。子节点区间都是父节点区间的二分子集。线段树适合的题型有非常鲜明的特点,即一个大区间的问题可以分解为若干小区间来求解并归并。比如一个大区间的SUM,等于两个子区间的sum的和;再比如,一个大区间的max,等于两个子区间的max的最大值。构建线段树的时间复杂度、空间复杂度都是O(2n),查询、更新(单个元素)是o(logn),总和性能不错。线段树的模板比较长,常见的操作包括buildTree, modifyTree 和queryTree。但是思路非常清晰简洁。如果写熟练了,并不比TrieNode模板慢。

概述:  类似于区间树,它在各个节点保存一个线段(数组中的一段子数组),主要用于高校解决连续区间的查询问题,由于二叉树的特性,基本能够保持每个操作的复杂度为O(logn)。线段树的每个节点表示一个区间,子节点则分别表示父节点的左右半区间,例如,父亲的区间是[a,b], 那么(c = (a+b)/2)左儿子的区间是[a,c], 右儿子的区间是[c+1, b].

由于线段树的父节点区间是平均分割到左右子树,因此线段树是完全二叉树,对于包含n个叶子节点的完全二叉树,它一定有n-1个非叶节点,总共2n-1个节点,因此存储线段是需要的空间复杂度是O(n)。

可以参考博客 和 这篇

 

 

 非递归的解题思路:视频教程

/*
线段树,类似于区间树,主要用来高效解决连续区间的动态查询问题,比如求数组区间的最大值、最小值、和的一些问题
*/
class NumArray {
private:
    int size;  // 存贮nums的长度值
    int *arr;  // 用来创建线段树的数组存储,这俩个值需要在构造函数中初始化
public:
    void buildTree(vector<int>& nums){   // 用来创建线段树
        for (int i = size, j = 0; j < nums.size(); i++, j++){      // 初始化线段树的后半部分,也就是树的叶子节点(数组原本的值)
            arr[i] = nums[j];
        }
        for (int i = size - 1; i > 0; i--){                     // 初始化线段树的前半部分,即树的非叶子节点(父节点),节点的值保存 其左右子节点的值的和
            arr[i] = arr[i * 2] + arr[i * 2 + 1];             
        }
    }
    NumArray(vector<int> nums) {  // 构造函数,注意私有成员变量的使用
        size = nums.size();
        if (size > 0){
            arr = new int[size * 2];      // 动态开辟数组空间, 为什么创建的线段树是原来数组长度的2倍值
                                               // 对于包含n个叶子节点的完全二叉树,它一定有n-1个非叶节点,总共2n-1个节点?
            buildTree(nums);                   // 首先先建造一个树
        }
    }
    
    void update(int i, int val) {
        i += size;                            // 转换到树中的新的索引值
        arr[i] = val;                         // 更新叶子节点的值
        while ( i > 0){                       // 更新父节点的值,先判断是左子节点还是右子节点
            int left = i;
            int right = i;
            if (i % 2 == 0){
                right++;
            }else{
                left--;
            }
            arr[i/2] = arr[left] + arr[right];  // 更新父结点的值
            i /= 2;
        }
    }
    
    int sumRange(int i, int j) {            // 因为父节点存放的肯定是从一个树的左子结点到另一个的右子节点,所以要先判断i和j 是不是左子节点和右子结点
        i += size;                          // 先将索引转换到线段树的数组索引中
        j += size;
        int sum = 0;
        while ( i <= j){                    // 相当于是找最小公共父结点???
            if (i % 2 != 0){                // 若i是右子结点,则加上这个值,i加1之后变成左子结点
                sum += arr[i];
                ++i;
            }
            if (j % 2 == 0){                // 若j是左子结点,则加上这个值,j减1之后变成右子结点
                sum += arr[j];
                --j;
            }
            i /= 2;                        // i 和 j 往上更新
            j /= 2;                 
        }
        return sum;                        // 为什么返回的是sum值,不是结点值? 最后一次i 和 j相等的时候 j变为左子结点,这时候会把arr[j]的值加上
    }
};

/**
 * Your NumArray object will be instantiated and called as such:
 * NumArray obj = new NumArray(nums);
 * obj.update(i,val);
 * int param_2 = obj.sumRange(i,j);
 */

递归解法(线段树):

 

树状数组:

posted @ 2017-10-24 17:42  爱简单的Paul  阅读(206)  评论(0编辑  收藏  举报