1. 程式人生 > >❗️❗️❗️LeetCode 315 Hard 逆序數計算 Python

❗️❗️❗️LeetCode 315 Hard 逆序數計算 Python

方法一:

演算法:分治/歸併排序

    思路

        利用分治的思想,歸併排序時當前下標i後有多少個元素要把位置提到i之前就是其逆序數,

        在排序過程中記錄逆序數的個數。⬅️其實比較難發現這個特徵我覺著

 

        比如左右已經排序好的兩個序列left = [-7,1,5,9],right = [-2,1,3,5]

            觀察左右兩個序列,以下描述中left中的值用i索引,right中的值用j索引

            對left和right中的元素,每個元素在當前位置的逆序數都是0,因為在本列表中,已經是

        從小到大排列的了,意味著右邊不會有比自身小的數,但是二者進行歸併排序的時候,兩個陣列

        之間會有大小的比較,進而產生逆序數

            在歸併排序中,舉例進行比較,

            當排序執行到過程i,j位置時

            如果left[i] <= right[j]:

                1. 在res中插入較小的元素left[i],res.append(left[i])

                2. left[i]在left中的逆序數是0,因為left已排序,i後面的肯定比它大,所以此時

                看j在right中的情況,當left[i] <= right[j]時,因為歸併排序是誰小誰就

                在前面,第j個位置的right比righ中的前j個元素大,即在前面的多次比較中,輪到

                第j個元素時,left[i] 才小於等於right[j],說明之前left[i]都是大於right[0..j-1]的,

                否則在j之前left[i]就插入了,所以此時left[i]的逆序數就是right[j]的下標j

                3. 這裡把等於的情況也放在這裡是因為,對於兩個元素相等時,逆序數的計算是和小於<時

                一樣,所以把它放在<=這裡,>的情況單獨是else的另一類

                    其實以等於=的情況來看更清晰,在第i,j個位置處二者才相等,說明在right中該值

                    大於j個元素,那麼left[i]的逆序數自然就是j了,而把相等的left[i]放進來後,後面right[j]

                    再進其逆序數也就自然而然是0了(res插入left[i]後,i++1,left和right中都至少大於

                    等於right[j]了)

            否則left[i] > right[j]:

                1. 在res中插入較小的元素right[i],res.append(right[j])

                2. 由於right已經排序,right[j]必然小於j以後的元素,在right中逆序數是0,

                又因為right[j]比left[i]還要小,left中後面還會進行比較的元素一定大於left[i],

                也就是說j這個位置的話,日後不論right還是left,都比當前j要大,逆序數是0

 

            當排序後left還有剩餘的話,說明剩餘的left元素每個元素都大於整個j的長度,

                如left=[5,6,7,8,9],right=[1,2,3,4,5],排序過程中一直插入right的值,最後left剩下的元素

                逆序數值+= right最後的j值,這個例子中最後j加到了5(所以才超出while的條件)

 

        注意

                1. 排序的過程中會打亂下標,而最後返回的是原下標對應的逆序數計數值,所以在排序中行程原nums的pair

            對(nums[i],i)

                2. 逆序數的新增是+=,而不是單純的 = ,上面的例子聽起來似乎是用=,是因為這只是一次歸併排序,實際上

            歸併排序會迭代很多層,每一次迭代都是會讓left和right變成一個新的排序後的組,而剛才也分析過了,對已經排序

            後的組,組內每個元素的逆序數是0,所以它們的逆序數值事實上是上一輪歸併前計算的,所以每一層迭代下來逆序數

            的統計是+=

 

    複雜度分析

        時間:ONlogN,歸併排序的時間

        空間:ON,儲存逆序數陣列的大小

def countSmaller( nums):
    def mergesort(left, right, count):
        result = []
        i = 0
        j = 0
        while i < len(left) and j < len(right):
            if left[i][0] <= right[j][0]:
                result.append(left[i])
                count[left[i][1]] += j
                i += 1
            else:
                result.append(right[j])
                j += 1
        for ii in range(i,len(left)):
            count[left[ii][1]] += j
            result.append(left[ii])
        if j < len(right):
            result.extend(right[j:])
        return result

    def merge(nums, count):
        if len(nums) < 2:
            return nums
        half = len(nums) // 2
        left = merge(nums[:half],count)
        right = merge(nums[half:],count)
        return mergesort(left, right, count)

    pairs = [(nums[i], i) for i in range(len(nums))]
    count = [0]*len(nums)
    merge(pairs, count)
    return count

 

方法二:

    二分查詢

def countSmaller1(self, nums):
    """
    BST Method
    利用 二分查詢來做!
    對列表元素倒著挨個插入一個列表構成有序列表,插入時該節點的插入位置即為比該節點小的節點數目
    要注意的是,要用bisect_left來找,bisect_left和bisect_right的不同在於對同一數值的元素
    bisect_left返回左值,bisect_right返回右值
    如
        r = [1,2,3,4,5],插入4,
            bisect_left 返回3,bisect_right返回4
    """
    import bisect
    r = []
    res = [0] * len(nums)
    for i in range(len(nums) - 1, -1, -1):
        bisect.insort(r, nums[i])
        res[i] = bisect.bisect_left(r, nums[i])
    return res

方法三:

    二叉搜尋樹BST
    Disscussion Method
    用二叉搜尋樹BST來做!
    [5, -7, 9, 1, 3, 5, -2, 1] 的逆序數可以看做把列表倒過來
    [1, -2, 5, 3, 1, 9, -7, 5]的每個元素前面有幾個元素比它小!
        可以用BST的特性,BST根節點左子樹的節點值總小於根節點,右子樹節點值總大於根節點
    以nums的倒過來後的順序構建BST,並且在構建過程中記錄比當前值小的節點有幾個,亦即左子樹節點大小
    所以在插入節點的時候,就可以更新根節點的左子樹數目值

class TreeNode:
    """
    定義BST樹節點的形式,count記錄值為val的節點有幾個,來處理值val相同的節點時的情況
    leftTreeSize記錄的是:
        此時此刻,t時刻,val這麼大的節點的左子樹節點數目,因為遞迴插入的時候,可以看做節點會流入root節點並向左
    走,這樣root節點的左子樹數目就多了一個,所以用leftTreeSize來記錄
    """
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None
        self.count = 1
        self.leftTreeSize = 0
class BinarySearchTree:
    """
    定義BST樹
    insert()函式返回的就是向BST樹插入值為val的節點時,該節點的逆序數的數目
    注意,此時此刻leftTreeSize和count已記錄了某一節點root處的左子樹節點數目和root同值的節點數量
    so
    if val == root.val:
        root.count += 1
        return root.leftTreeSize
    val < root.val:
        root.leftTreeSize += 1
        if root 沒有左子樹,則以val構建左子樹,並且它是最小的,return 0
        否則
            return 向左遞迴,因為val < root.val,所以一定不會加已經遍歷過得節點的leftTreeSise什麼的
    否則 val > root.val
        if root 沒有右子樹,則以val構建右子樹,並且它是當前最大的,
            return root.leftTreeSise + root.count -->把root的左子樹數量和自己獻祭出去
        否則:
            return root.leftTreeSise + root.count + 繼續向右遞迴,把當前層的root左子樹和自己交出去,並且還要加下一層的
    """
    def __init__(self):
        self.root = None
    def insert(self, val, root):
        if not root:
            self.root = TreeNode(val)
            return 0
        if val == root.val:
            root.count += 1
            return root.leftTreeSize

        if val < root.val:
            root.leftTreeSize += 1
            if not root.left:
                root.left = TreeNode(val)
                return 0
            else:
                return self.insert(val, root.left)
        else:
            if not root.right:
                root.right = TreeNode(val)
                return root.count + root.leftTreeSize
            else:
                return root.count + root.leftTreeSize + self.insert(val, root.right)
class Solution:
    def countSmaller(self, nums):
        tree = BinarySearchTree()
        result = [tree.insert(nums[i], tree.root)for i in range(len(nums) - 1, -1, -1)]
        result = result[::-1]
        return result