1. 程式人生 > >leetcode327(樹狀數組/歸並排序)

leetcode327(樹狀數組/歸並排序)

求和 part pre 同時 () 給定一個整數數組 span 樹狀 tro

問題描述

給定一個整數數組nums,返回其所有落在[low, upper]範圍內(包含邊界)的區間和的數目。
區間和sums(i, j)的定義為所有下標為i到j之間(i ≤ j)的元素的和,包含邊界。

思路:

歸並排序

問題可描述為,對每個sums[i],滿足lower<=sums[i] - sums[j]<=upper的j的個數,並進行求和。

一個很巧妙的方法是,對sums進行排序得到ocums,然後求ocums[m] - ocums[n] = sums[i] - sums[j]落在上下界的個數,這裏想強調的是ocums[m],ocums[n]分別對應排序前的sums[i],sums[j]。 這樣做的好處是排序後求ocums區間內元素個數要方便很多,比如說對ocums[m],只需找到剛好滿足上界的ocums[y],和剛好滿足下界的ocums[x],x-y即為個數(註意ocums是升序的,所以x一定大於y),找到上下界的方法如果不用循環線性查找(就是從小到大一個個試),就可以用算法復雜度更低的二分查找,這種思路用於歸並排序。

歸並排序的思路是,把區間劃分為上下兩個半空間x和y,然後對兩個空間的數一個一個取x[i]和y[j],比較這兩個數,如果x[i]<y[j]就讓i+1,否則j+1,並把較小的數加入到新序列中,這樣實現排序。當前排序的空間是從小到大的,也就是說需要先把區間劃分到最小,然後慢慢合並。

用歸並排序的思路求所有的區間和,就是先把區間sums劃分到最小,然後求滿足區間和上下界的個數,然後對區間進行排序,然後合並兩個小區間,求滿足區間和上下界的個數,然後對合並後區間進行排序,然後又合並排序後的兩個區間。。。循環下去;從一棵樹的角度來說,就是從根節點開始劃分區間,從最底層的區間開始計算個數並排序,並向上排序,因此這是一個遞歸的過程,求個數的過程是在合並的過程中計算的。

# 歸並排序
# 讓人不解的兩個點
# 1. 為什麽最小只劃分區間到兩個數sums[i, i+1],而不是一個數?
# 這是因為操作的對象是和,同時要遵循下半區間減上半區間
# 當只有兩個數的時候,實質是看nums[i] = sums[i + 1] - sums[i]是否在上下界內
# 最小區間劃分不會重疊,也就不會出現sums[i - 1, i],那還有一個數nums[i - 1]呢
# 這個數會在相鄰兩個最小區間合並的過程中被考慮(想象一棵二分樹)
# 2. 對sums進行排序後得到ocums,選擇區間和ocums[i] - ocums[j]在上下界的,這個合理嗎
# 問題也說了,區間和就等於sums[m] - sums[n],本質上還是求源序列的區間和在[m, n]上是否滿足上下界
# 這個方法真的很妙啊
# 最後要說這個的算法復雜度是N方的,真不知道怎麽就通過了
class Solution(object):
    def countRangeSum(self, nums, lower, upper):
        """
        :type nums: List[int]
        :type lower: int
        :type upper: int
        :rtype: int
        """
        size = len(nums)
        sums = [0] * (size + 1)
        for x in range(1, size + 1):
            sums[x] += nums[x - 1] + sums[x - 1]
        INF = max(sums)

        def mergeSort(lo, hi):
            if lo == hi: return 0
            mi = int((lo + hi) / 2)
            cnt = mergeSort(lo, mi) + mergeSort(mi + 1, hi)
            x = y = lo
            for i in range(mi + 1, hi + 1):
                while x <= mi and sums[i] - sums[x] >= lower:
                    x += 1
                while y <= mi and sums[i] - sums[y] > upper:
                    y += 1
                cnt += x - y
            part = sums[lo : hi + 1]

            l, h = lo, mi + 1
            for i in range(lo, hi + 1):
                x = part[l - lo] if l <= mi else INF
                y = part[h - lo] if h <= hi else INF
                if x < y: l += 1
                else: h += 1
                sums[i] = min(x, y)
            return cnt

        return mergeSort(0, size)

nums = [-2, 5, 1, 3, 1, -4, 2]
lower = -2
upper = 2
X =Solution()
print(X.countRangeSum(nums, lower, upper))

樹狀數組

還可以對問題就行轉換,即求滿足sums[i] - upper<=sums[j]<=sums[i] - lower的j的個數,最樸素的方法就是對每一個sums[i]遍歷所有的(j,j<i)

# leetcode 327:求所有區間和在[lower, upper]之間的個數
# 解法1 kk...復雜度為N方,超時了
class Solution(object):
    def countRangeSum(self, nums, lower, upper):
        """
        :type nums: List[int]
        :type lower: int
        :type upper: int
        :rtype: int
        """
        ans = 0
        sum = [0 for _ in nums]
        for i, num in enumerate(nums):
            if i == 0:
                sum[i] = num
            else:
                sum[i] = sum[i - 1] + num
            if sum[i] >= lower and sum[i] <= upper:
                ans += 1
            for j in range(i):
                if sum[j] >= (sum[i] - upper) and sum[j] <= (sum[i] - lower):
                    ans += 1
        return ans

nums = [-2, 5, 1, 3, 1, -4, 2]
lower = -2
upper = 2
X =Solution()
print(X.countRangeSum(nums, lower, upper))

但用更直白的描述就是,求位於(sums[i] - upper, sums[i] - lower)的sums[j]的個數,希望通過排序知道每個值取了多少次,都排在哪個位置,當然前提是每次排序都只有序列的前i個數。也就是說,對sums[i]求個數的時候,只需把sums[i]的值插入到前面已經排好序的新序列中(這個可以用二分法做)。由於我們的目的是對個數求和,可以讓序列的值為個數,然後序列的索引為sums[i],這樣,樹狀數組的結構就出來了。

# 基於上一思路,利用樹狀數組來降低復雜度
# 同樣是為了求落在區間[sumi - upper, sumi - lower]的sumj的個數,
# 前一樸素法是對每個sumi,直接for j in range(0, i),致使算法復雜度為O(N^2)
# 樹狀數組的思想是先對sums進行去重排序得到長為m的序列,然後構建一個與該序列等長的樹狀數組來記錄每個值出現的次數
# 於是,要計算落在區間[sumi - upper, sumi - lower]的sumj的個數,只需對該數組求區間和
# 可行性的原因是,在for sumi in sums循環中,樹狀數組是隨著sumi不斷生成的,當前樹狀數組只記錄了(x, x<=i)的sumx
# 對於每個sumi,算法復雜度為O(logN),總的算法復雜度為O(NlogN)
import bisect
class Solution(object):
    def countRangeSum(self, nums, lower, upper):
        """
        :type nums: List[int]
        :type lower: int
        :type upper: int
        :rtype: int
        """
        ans = 0
        sums = [0 for _ in nums]
        for i, num in enumerate(nums):
            if i == 0:
                sums[i] = num
            else:
                sums[i] = sums[i - 1] + num
        oscums = sorted(set(sums))
        ft = FenwickTree(len(oscums))
        for sumi in sums:
            left = bisect.bisect_left(oscums, sumi - upper)
            right = bisect.bisect_right(oscums, sumi - lower)
            ans += ft.getSum(right) - ft.getSum(left) + (lower <= sumi <= upper)
            ft.add(bisect.bisect_right(  oscums, sumi), 1)
        return ans
class FenwickTree(object):
    def __init__(self, n):
        # n 為數組的長度
        self.n = n
        self.c = [0 for _ in range(n + 1)]
    def lowBit(self, x):
        return x & (x ^ (x - 1))
    def add(self, i, val):
        while i <= self.n:
            self.c[i] += val
            i += self.lowBit(i)
    def getSum(self, i):
        sum = 0
        while i > 0:
            sum += self.c[i]
            i -= self.lowBit(i)
        return sum

nums = [-2, 5, -1]
lower = -2
upper = 2
X =Solution()
print(X.countRangeSum(nums, lower, upper))

參考博文:

http://bookshadow.com/weblog/2016/01/11/leetcode-count-of-range-sum/

leetcode327(樹狀數組/歸並排序)