🌸题目
🍁给定一个整数数组 nums,按要求返回一个新数组 counts。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。
示例:
1 | 输入: [5,2,6,1] |
解释:
- 5 的右侧有 2 个更小的元素 (2 和 1).
- 2 的右侧仅有 1 个更小的元素 (1).
- 6 的右侧有 1 个更小的元素 (1).
- 1 的右侧有 0 个更小的元素.
🌸分析
这道题要用排序的思路来解决。快速查找和更新,使用递归或树的结构可以高效实现。
🌸解法一:冒泡/暴力解法
暴力求解,不解释。
稳定的排序方法都可求解这个问题。
冒泡排序每交换一次消除一对逆序,使用一个索引数组记录消除的次数。
时间复杂度O(n2),超时。
1 | public static List<Integer> countSmaller(int[] nums) { |
🌸解法二: 插入排序
从右往左进行插入排序,
根据插入的位置计算右边小于该元素的个数。
优化:先使用二分法查找位置,再插入。可以降低内循环查找的时间复杂度O(nlogn),但是元素交换的次数还是O(n2)。
1 | public List<Integer> countSmaller(int[] nums) { |
🌸解法三: 归并排序+索引数组
求解 “逆序对” 的关键在于:当其中一个数字放进最终归并以后的有序数组中的时候,这个数字与之前看过的数字个数(或者是未看过的数字个数)可以直接统计出来,而不必一个一个数”。
回到本题,本题让我们求 “在一个数组的某个元素的右边,比自己小的元素的个数”,因此,我们就 应该在 “前有序数组” 的元素出列的时候,数一数 “后有序数组” 已经出列了多少元素,因为这些已经出列的元素都比当前出列的元素要小(或者等于)。
分析
不过题目中要求我们要具体计算到元素级别。“归并排序” 完成以后,原始数组的位置就已经变化了,因此如何定位元素是关键。
一个元素在算法的执行过程中位置发生变化,我们还想定位它,就是最小索引堆,使用 “索引数组” 的关键在于:
- “原始数组” 不变,用于比较两个元素的大小,真正位置变换的是 “索引数组”。
为了完成 “索引数组” 的归并,我们还需要一个 “索引数组” 长度的临时数组,把索引数组的值复制过去,比较完成以后,再赋值回 “索引数组”。
注意事项
- 如果 “前有序数组” 和 “后有序数组” 直接合并的时候,就有序,就不必归并
- 在 “归并” 的时候,全局使用一个临时存储数组,而不必每一个归并都新建临时的存储空间。
- 出列一个元素的时候,马上得到右边比自己小的元素的个数,是通过不同的指针之间的距离得到的。
java
1 | import java.util.ArrayList; |
python
1 | class Solution: |
🌸解法四: 二叉搜索树
使用二叉搜索树也可以完成插入并统计的功能,从右往左构建二叉树。
递归实现:
当走到右节点时,
- 统计根节点和左节点的个数,
- 继续插入并统计右边是否还有较小值;
当走到左节点或者根节点时,
- 计数器加一
- 继续插入并统计左边是否还有较小值。
遍历一遍就可以完成搜索,时间复杂度O(nlogn)。
1 | class Solution { |
🌸解法五: 树状数组
🍁前言
- 「树状数组」属于高级的数据结构,如果是非竞赛选手和普通公司面试,可以不用掌握
- 「树状数组」这个数据结构用于高效地解决「前缀和查询」与「单点更新」问题
🍁离散化
首先对数组元素做预处理,这一步叫「离散化」。
- 考虑到「树状数组」的底层是数组(线性结构),为了避免开辟多余的「树状数组」空间,需要进行「离散化」
- 「离散化」的作用是:针对数值的大小做一个排名的「映射」,把原始数据映射到
[1, len]
这个区间,这样「树状数组」底层的数组空间会更紧凑,更易于维护。
🍁分析
因为我们关心「当前位置的右边比当前数值小的元素的个数」,因此可以设计如下的算法流程:
- 从右向左读取排名;
- 先查询严格小于当前排名的「前缀和」,这里「前缀和」指的是,严格小于当前排名的元素的个数,这一步对应「前缀和查询」;
- 然后给「当前排名」加 11,这一步对应「单点更新」。
我们根据上面的步骤,针对 [5, 2, 6, 1] 得到排名 [3, 2, 4, 1] ,把具体的计算过程写一下:
- 第 1 步:读到 1 。
1 的排名是 1 ,首先先在「树状数组」的下标 1 位置更新,执行的操作是 +1,很明显,在排名 1 之前肯定没有数了(查询排名在 1 之前的数有多少个),所以在结果数组的最后一个位置填 0。- 第 2 步:读到 6。
6 的排名是 4,首先先在「树状数组」的下标 4 位置更新,执行的操作是 +1,接下来在「树状树组」里面执行一次查询,查询在排名 4 之前的元素个数有多少,结果是 1,所以在结果数组的倒数第 2 个位置填 0;- 第 3 步:读到 2。
2 的排名是 2,首先先在「树状数组」的下标 2 位置更新,执行的操作是 +1+1,接下来在「树状树组」里面执行一次查询,查询在排名 2 之前的元素个数有多少,结果是 1,所以在结果数组的倒数第 3 个位置填 11;- 第 4 步:读到 5。
5 的排名是 3,首先先在「树状数组」的下标 3 位置更新,执行的操作是 +1+1,接下来在「树状树组」里面执行一次查询,查询在排名 3 之前的元素个数有多少,结果是 2,所以在结果数组的倒数第 4 个位置填 2。- 于是 [2, 1, 1, 0] 即为所求。
🍁参考代码
1 | import java.util.ArrayList; |
python
1 | from typing import List |