• Leetcode 307. Range Sum Query


    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);
     */

    递归解法(线段树):

    树状数组:

  • 相关阅读:
    (十)条件判断
    (九)字符处理命令
    (八)awk命令
    (六)环境变量配置文件
    (七)grep命令行提取符号
    Ⅶ 类模板与STL编程 ②
    Ⅵ 虚函数与多态性
    Ⅴ 运算符重载
    Ⅳ 继承与派生②
    Ⅳ 继承与派生①
  • 原文地址:https://www.cnblogs.com/simplepaul/p/7724837.html
Copyright © 2020-2023  润新知