「樹狀數組」第 4 節: 相關例題

其實下面這兩個問題本質上是一個問題。

例 1:《劍指 Offer 》第 51 題:逆序數的計算

在數組中的兩個數字如果前面一個數字大於後面的數字,則這兩個數字組成一個逆序對。

輸入一個數組,求出這個數組中的逆序對的總數。

樣例

輸入:[1,2,3,4,5,6,0]

輸出:6

分析:這道題最經典的思路是使用分治法計算,不過使用樹狀數組語義更清晰一些。

Python 代碼:

class Solution(object):
    def inversePairs(self, nums):
        class FenwickTree:
            def __init__(self, n):
                self.size = n
                self.tree = [0 for _ in range(n + 1)]

            def __lowbit(self, index):
                return index & (-index)

            # 單點更新:從下到上,最多到 size,可以取等
            def update(self, index, delta):
                while index <= self.size:
                    self.tree[index] += delta
                    index += self.__lowbit(index)

            # 區間查詢:從上到下,最少到 1,可以取等
            def query(self, index):
                res = 0
                while index > 0:
                    res += self.tree[index]
                    index -= self.__lowbit(index)
                return res

        # 特判
        l = len(nums)
        if l < 2:
            return 0

        # 原始數組去除重複以後從小到大排序
        s = list(set(nums))

        # 構建最小堆,因爲從小到大一個一個拿出來,用堆比較合適
        import heapq
        heapq.heapify(s)

        # 由數字查排名
        rank_map = dict()
        index = 1
        # 不重複數字的個數
        size = len(s)
        for _ in range(size):
            num = heapq.heappop(s)
            rank_map[num] = index
            index += 1

        res = 0
        # 樹狀數組只要不重複數字個數這麼多空間就夠了
        ft = FenwickTree(size)
        # 從後向前看,拿出一個數字來,就更新一下,然後向前查詢比它小的個數
        for i in range(l - 1, -1, -1):
            rank = rank_map[nums[i]]
            ft.update(rank, 1)
            res += ft.query(rank - 1)
        return res

說明:中間將數字映射到排名是將原數組「離散化」,「離散化」的原因有 2 點:

1、樹狀數組我們看到,下標是從 11 開始的,我們不能保證我們的數組所有的元素都大於等於 11

2、即使元素都大於等於 11,爲了節約樹狀數組的空間,我們將之「離散化」可以把原始的數都壓縮到一個小區間。我說的有點不太清楚,這一點可以參考 樹狀數組 求逆序數 poj 2299

例2:「力扣」第 315 題:計算右側小於當前元素的個數

給定一個整數數組 nums,按要求返回一個新數組 counts。數組 counts 有該性質: counts[i] 的值是 nums[i] 右側小於 nums[i] 的元素的數量。

示例:

輸入: [5,2,6,1]
輸出: [2,1,1,0] 
解釋:
5 的右側有 2 個更小的元素 (2 和 1).
2 的右側僅有 1 個更小的元素 (1).
6 的右側有 1 個更小的元素 (1).
1 的右側有 0 個更小的元素.

分析:事實上,這個問題就是在計算「逆序數」,和上一個問題是一樣的。「計算右側小於當前元素的個數」我們可以「從後向前一個一個填」。因爲涉及大小關係,所以要排個序,並且給出排名(rank)。這一步操作也叫「離散化」。具體方法是:先畫出一個排名表,對於這個問題,排名表是:

排名
55 33
22 22
11 11
66 44

從後向前填:

1、遇到 1111 的排名是 11 ,首先先在 11 那個位置更新 11,那麼 11 之前肯定沒有數了,所以就是 00

2、遇到 6666 的排名是 44,首先先在 44 那個位置更新 11,那麼 66 之前可以在樹狀樹組裏面查一下,是 11

3、遇到 2222 的排名是 22,首先先在 22 那個位置更新 11,那麼 22 之前可以在樹狀樹組裏面查一下,是 11

4、遇到 5555 的排名是 33,首先先在 33 那個位置更新 11,那麼 33 之前可以在樹狀樹組裏面查一下,是 22

反過來就是結果 [2,1,1,0][2,1,1,0]

Python 代碼:

class Solution:
    def countSmaller(self, nums):
        """
        :type nums: List[int]
        :rtype: List[int]
        """

        class FenwickTree:
            def __init__(self, n):
                self.size = n
                self.tree = [0 for _ in range(n + 1)]

            def __lowbit(self, index):
                return index & (-index)

            # 單點更新:從下到上,最多到 size,可以取等
            def update(self, index, delta):
                while index <= self.size:
                    self.tree[index] += delta
                    index += self.__lowbit(index)

            # 區間查詢:從上到下,最少到 1,可以取等
            def query(self, index):
                res = 0
                while index > 0:
                    res += self.tree[index]
                    index -= self.__lowbit(index)
                return res

        l = len(nums)
        if l == 0:
            return []
        if l == 1:
            return [0]

        s = list(set(nums))
        import heapq
        heapq.heapify(s)
        index = 1

        size = len(s)
        rank_map = dict()
        ft = FenwickTree(size)
        for _ in range(size):
            num = heapq.heappop(s)
            rank_map[num] = index
            index += 1

        # 從後向前填表
        res = [None for _ in range(l)]
        for index in range(l - 1, -1, -1):
            rank = rank_map[nums[index]]
            ft.update(rank, 1)
            res[index] = ft.query(rank - 1)
        return res

(本文完)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章