力扣315,没有花里胡哨的题面,属于树状数组的模版题了。正好复习一下BIT。先看题目:
[计算右侧小于当前元素的个数]
Category | Difficulty | Likes | Dislikes |
---|---|---|---|
algorithms | Hard (43.45%) | 1032 | - |
给你一个整数数组 nums
,按要求返回一个新数组 counts
。数组 counts
有该性质: counts[i]
的值是 nums[i]
右侧小于 nums[i]
的元素的数量。
示例 1:
1 | 输入:nums = [5,2,6,1] |
示例 2:
1 | 输入:nums = [-1] |
示例 3:
1 | 输入:nums = [-1,-1] |
看完题目,很自然想到用树状数组来做。关于树状数组,可以参考这篇文章:算法学习笔记(2) : 树状数组 - 知乎 (zhihu.com)
思路大概是:
- 通过排序获得
num
中每个数在数组中的由小到大排位,该排位值就是该数在树状数组里的下标。 - 由于查询每个点时,我们只需要知道右侧元素中小于该数的元素数量。因此从右往左遍历数组,遍历到某一点时,可以保证右侧元素对应的树状数组位置已被更新过。先用
query
查询,再将用该数更新数组。 - 遍历完成,返回答案。
下面是代码。
1 |
|
除了用树状数组,还有没有其它O(nlogn)的算法呢?答案是有的。用归并排序求逆序对的数量也能解决本题。
首先定义逆序对。对于数组nums
的下标i
, j
,若i<j
且nums[i]>nums[j]
,则称(nums[i], nums[j])
是一对逆序对。题目中所要求的count[i]
,就是以nums[i]
为左边元素的逆序对的个数。这样问题就被转化为了求逆序对。
考虑由两个数组L
, R
,分别从两数组中取一个元素a
和b
,求使(a, b)
为逆序对的取法由多少种。如果暴力枚举,复杂度为O(n2),不符合我们的期望。然而,如果L
和R
是两个从小到大排列好的数组,问题将会很好解决。首先定义两个指针p1
和p2
,初始时分别指向L
和R
的第一个元素,然后执行以下算法:
1、右移p2
直到R[p2]>=L[p1]
。由于R[p2]
是数组R
中第一个大于等于L[p1]
的元素,即数组R
中p2
之前的元素均小于L[p1]
,均能与L[p1]
组成逆序对。故数组R
开头到p2
的元素个数就是能与L[p1]
组成逆序对的元素个数。
2、右移p1
,回到第一步求L中下一个元素能组成的逆序对个数。
该算法p1和p2均只经历一次遍历,时间复杂度为O(n),符合我们的要求。然而这个算法只能求分别从两个数组各取一个元素所能组成的逆序对,不能求在同一个数组中元素形成的逆序对。为使用上述算法,我们希望找到一个办法,让数组中的任意两个元素都有且仅有一次地被置于两个数组之中,并按上述算法求判断它们能否组成逆序对。这样我们不会遗漏任一逆序对,也不会重复计算。归并排序恰好能实现我们的期望。归并排序每次都会将数组分成两个小数组,通过递归调用分别将这两个小数组排好序,再合并这两个数组。如果我们在两个小数组排序好之后,对这两个数组执行计算逆序对的算法,之后再合并,能否满足要求呢?下面我们证明确实可以。
- 首先,证明任意两个元素都至少有一次被置于两个不同的数组中,且对这两个数组执行求逆序对算法。归并排序递归的终止状态是待排序的数组中只有一个元素。也就是说,数组中的每一个元素,最初都处于只含有自己一个元素的数组中。而在排序的最后,所有元素都处在同一个数组中。也就是说,数组中的任意两个元素,最初都在不同的数组中,然而有一个合并的过程,将它们所在的两个数组合并为一个数组。只要在该次合并之前执行求逆序对算法,就能保证任意两个元素都有一次被置于两个不同的数组中求逆序对。
- 接着,证明任意两个元素被分别置于两个数组求逆序对的经历只有一次。我们已经证明的该过程必有一次。而经历了这一次之后,两个元素被合并到同一个数组之中,以后不可能再被置于不同数组中了。因此可以保证只有一次。
- 容易知道,原数组中的任意两个元素
a
和b
,若a
位于b
的左侧,则在归并排序过程中,a
所在的数组和b
所在的数组在执行求逆序对过程中,a
所在数组一定在b
所在数组左侧。
证明完成。另外还有一点,排序之后元素顺序被打乱了,但我们需要知道每个元素在原数组中的下标,以将其所能组成的逆序对数量记录在数组counts的相应位置。我们可以将原数组中的元素换成二元组(元素值,下标)
。如此我们在排序过程中就能知道元素在原数组中的下标了。至此,思路已经很清晰了,下面看代码。
1 |
|