leetcode327(樹狀數組/歸並排序)
問題描述:
給定一個整數數組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(樹狀數組/歸並排序)