聽說是經典題,特地來寫寫,仔細一看,這竟是我前陣子練習線段樹時有看過的題目,但那時候還真想不出怎麼做。

題目

輸入整數陣列nums,你必須回傳一個新的counts陣列。
counts[i]代表著nums[i]右側較小元素的數量。

解法

初見時只有一個想法:O(N^2)暴力法,但是N會到10^5,是絕對不可能過的。

想想既然要找右邊的較小元素,那應該是要從右邊往遍歷。
對於遍歷到的數字n,需要一個資料結構將其計數+1,然後查找小於n的數字總出現次數有多少。
再來想想看什麼資料結構可以快速查詢區間和:segment tree和binary indexed tree。

線段樹前陣子用過不少次,這次改用樹狀陣列BIT。
BIT是一種索引以0開始的可變區間和查詢資料結構,但是這題的數字範圍是[-10^4, 10^4],所以需要額外加上10^4的位移,將範圍調整成[0, 2*10^4]。
從右邊往左遍歷每個數字n,將其調整為n+offset,並查詢BIT中小於n+offset的數有幾個,更新至ans[i],最後將n+offset的計數+1即可。

class BinaryIndexedTree:

    def __init__(self, nums: List[int]):
        self.bit = [0]+nums  # restore range sum
        self.nums = nums  # original list
        self.N = len(self.bit)

    # add value to a certain index  
    def update(self, index: int, val: int) -> None:
        self.nums[index] += val
        index += 1
        while index < self.N:
            self.bit[index] += val
            index = index + (index & -index)

    # get prefix sum from 0 to index
    def prefixSum(self, index: int) -> None:
        index += 1
        res = 0
        while index > 0:
            res += self.bit[index]
            index = index - (index & -index)
        return res


class Solution:
    def countSmaller(self, nums: List[int]) -> List[int]:
        offset=10**4
        bit=BinaryIndexedTree([0]*(offset*2+5))
        N=len(nums)
        ans=[0]*N
        for i in reversed(range(N)):
            n=offset+nums[i]
            ans[i]=bit.prefixSum(n-1)
            bit.update(n,1)

        return ans

偷雞解法:使用sorted list,速度竟然比BIT還快,媽呀。
一樣從右方往左遍歷回來,對於每個數字n,在sl裡面找到最後一個小於等於n-1的索引位置idx,也就是bisect_right在-1。
該idx再加1就是小於等於n-1的元素數量,可以把-1和+1抵銷,直接變成bisect_right(n-1)。

from sortedcontainers import SortedList

class Solution:
    def countSmaller(self, nums: List[int]) -> List[int]:
        sl=SortedList()
        N=len(nums)
        ans=[0]*N
        for i in reversed(range(N)):
            n=nums[i]
            ans[i]=sl.bisect_right(n-1)
            sl.add(n)

        return ans