(LeetCode 307) Range Sum Query - Mutable(樹狀數組講解)

Q:
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:
The array is only modifiable by the update function.
You may assume the number of calls to update and sumRange function is distributed evenly.

題意大致是,給你提供一個二維數組,然後求指定下標之間的數之和,已知數組中的值可以更新,並且更新和求和操作會被頻繁調用

Solution;
最原始的想法就是暴力遍歷求和,不過想也不用想,當求和操作操作頻繁調用,會超出給定時限。
並且,數組中的值會被頻繁更新,我瞭解到的解決方法有Segement Tree(線段樹),Binary Indexed Tree(樹狀數組) ,和平方根分解三種辦法。樹狀數組真的是神作,很精巧的辦法,也很高興能夠了解並學習這些數據結構。

想了解這道題Segement Tree(線段樹)解法的可以參考這篇博客:
(LeetCode 307) Range Sum Query - Mutable(Segment Tree)

本篇博客提供樹狀數組的解釋以及解法:
樹狀數組對於求元素值可變數組指定下標之間值之和的問題十分適合,當然也可應用在求最大值或者最小值這類的問題中。

如果不需要了解樹狀數組,可以直接翻到最後,會有AC源碼。

要了解樹狀數組,我們先看它的存儲形式:
這裏寫圖片描述
圖片來源:樹狀數組

我們可以看到當i 爲奇數時,c[i]=a[i] 。當i 爲偶數時,我們就需要算出i 的分解因子中爲2的多少次方了,這個不好解釋。
舉個例子:

  1. i=4 時,i=221 ,所以C[4]=A[1]+A[2]+A[3]+A[4]
  2. i=6 時,i=213 ,所以C[6]=A[5]+A[6]
  3. i=8 時,i=231 ,所以C[8]=A[1]+...+A[8]
  4. i=9 時,i=203 ,所以C[6]=A[9]

不知道大家看懂了沒有,其實對於奇數偶數都是這麼算的。將i 分解因式,得到它的因式中可以表示爲2次方的那個因式,C[i] 就等於當前位向前加那麼多位。

那麼那個因式怎麼得到呢?
有一個簡單的公式: i &(i)

注意,樹狀數組的下標從1開始。
所以,樹狀數組中的幾個核心函數分別是lowbit,sum, change。這幾個函數短小精悍但是力大無窮啊,如果大家能夠看懂這幾段代碼,相信會被它的思想深深的折服。我不建議直接背,但是你理解代碼後,也很容易寫出來。

int lowbit(int x){
        return x&(-x);
    }
void change(int i, int val){
        while(i<c.size()){
            c[i]+=val;
            i+=lowbit(i);
        }
    }
void sum(int n){
    int sum=0 ;
    while(n>0){
        sum+=c[n];
        n-=lowbit(n);
    }
    return sum;
}

下面將它應用到這道題中。
需要注意的是:上面的change函數,是在原來值的基礎上加上一個數,而不是直接替換掉原來的那個數,在update時需要注意。

class NumArray {
public:
    vector<int> sums;
    vector<int> nums;
    int lowbit(int x){
        return x&(-x);
    }
    void change(int i, int vals){
        while(i<sums.size()){
            sums[i]+=vals;
            i+=lowbit(i);
        }
    }

    int sum(int n){
        int sum=0;
        while(n>0){
            sum+=sums[n];
            n-=lowbit(n);
        }
        return sum;
    }

    NumArray(vector<int> &nums) {
        this->nums = nums;
        sums.resize(nums.size()+1);
        for(int i=0;i<nums.size();i++){
            change(i+1,nums[i]);
        }
    }
    void update(int i, int val) {
        int delta = val-nums[i];
        nums[i] = val;
        change(i+1,delta);
    }
    int sumRange(int i, int j) {
        return sum(j+1)-sum(i);
    }
};

時間消耗會比使用線段樹少一點。。。。

Reference Link:
http://www.cppblog.com/Ylemzy/articles/98322.html
http://www.cnblogs.com/zhangshu/archive/2011/08/16/2141396.html
http://www.cnblogs.com/yrbbest/p/5056739.html
http://blog.csdn.net/fly_yr/article/details/50276487

發佈了45 篇原創文章 · 獲贊 15 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章