❗️❗️❗️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