如何使用二叉索引树(BIT)统计数组中满足nums[i]>2*nums[j]的逆序对?BIT应用与数据离散化的概念性问询
如何使用二叉索引树(BIT)统计数组中满足nums[i]>2*nums[j]的逆序对?BIT应用与数据离散化的概念性问询
嘿,我来帮你把这个问题拆明白,咱们从BIT的核心作用说起,再聊离散化的必要性,尽量用通俗的逻辑讲清楚~
一、BIT怎么帮我们统计符合条件的逆序对?
首先得回忆下BIT(二叉索引树/ Fenwick树)的核心能力:它能快速做单点更新和前缀/后缀频率查询,这刚好戳中了逆序对统计的痛点——我们需要高效追踪已处理元素的分布,快速算出有多少元素满足某个大小条件。
咱们的目标是找所有i < j且nums[i] > 2*nums[j]的对,换个遍历思路就好理解了:
- 我们从数组末尾往开头遍历(也就是先处理j更大的元素)
- 每处理到一个元素
nums[i]时,我们需要知道:已经被我们加入BIT的元素(也就是原数组中j > i的那些元素)里,有多少个满足nums[j] < nums[i]/2(因为nums[i] > 2*nums[j]等价于nums[j] < nums[i]/2) - 这个查询到的数量,就是以
i为左端点的符合条件的逆序对数目,把所有查询结果累加起来就是总数 - 查完之后,把当前的
nums[i]插入BIT(也就是更新BIT中对应值的频率+1),让前面的元素(更靠左的i')能查询到它
举个小例子直观感受:
比如数组是[3,1,2],从后往前遍历:
- 先处理
2:BIT是空的,查询<1的元素数量为0,累加0;把2加入BIT - 再处理
1:查询<0.5的元素数量为0,累加0;把1加入BIT - 最后处理
3:查询<1.5的元素数量为1(就是已经加入的1),累加1;把3加入BIT
最终总数是1,对应逆序对(0,1)(3>2*1),完全正确。
BIT在这里的优势是把每次查询和更新的时间压缩到了O(log k)(k是离散化后的数值范围大小),整个算法的时间复杂度就是O(n log n),比暴力的O(n²)高效太多。
二、数据离散化在这里的作用是什么?
你肯定会疑惑:为啥要做离散化?直接用元素值当BIT的索引不行吗?
答案是:如果数组里的元素范围很大(比如到1e9),我们根本不可能创建一个大小为1e9的BIT——内存直接爆掉。而离散化的核心就是把大数值范围压缩成我们能处理的小范围,同时保留数值的相对大小关系(毕竟我们只需要比较大小,不需要用到数值本身的具体值)。
具体到这个问题,离散化的步骤是这样的:
- 收集所有需要比较的数值:因为我们的条件涉及
nums[i]和2*nums[j]的比较,所以要把数组里的每个元素nums[x],以及每个元素的2倍值2*nums[x]都收集到一个列表里 - 排序去重:把这个列表排序,然后去掉重复值,得到一个有序的唯一值列表
- 映射索引:对于任意一个值
v,用二分查找找到它在这个有序列表中的位置,这个位置就是它在BIT中的索引
为啥要包含2*nums[x]?因为我们的查询条件本质是比较nums[i]和2*nums[j]的大小,所以这些2倍值也是我们需要参与排序比较的对象,只有把它们都加入离散化的列表,才能保证我们的大小比较逻辑是正确的。
还是用刚才的[3,1,2]例子:
- 收集的数值是
3,6,1,2,2,4 - 排序去重后是
[1,2,3,4,6] - 每个值对应的索引(假设BIT从1开始计数):1→1,2→2,3→3,4→4,6→5
这样BIT的大小只需要5,完全可以轻松处理。
总结一下
- BIT的作用:高效维护已处理元素的频率分布,快速查询满足
nums[j] < nums[i]/2的元素数量,避免暴力枚举的低效 - 离散化的作用:把超大的数值范围压缩到BIT可处理的大小,同时保留数值的相对大小,保证比较逻辑的正确性
备注:内容来源于stack exchange,提问作者Vishal Jangid
相关产品推荐
相关产品推荐

