前言

看网上都说这个题目很经典,但是自己最初根本没办法将区间求和与求逆序对联系起来,思考了许久,此处进行记录并方便后来着理解。
注:本文的讲解建议配合​​​线段树之逆序对问题​​代码来看。

题目

​剑指 Offer 51. 数组中的逆序对​

逆序对

在一个线段树求逆序对的详情理解_线段树序列中,如果线段树求逆序对的详情理解_leetcode_02线段树求逆序对的详情理解_逆序_03,且称线段树求逆序对的详情理解_逆序对_04线段树求逆序对的详情理解_逆序对_05是一个逆序对,然后求该nums序列中逆序对的个数。

分析如下

int k = 0;
for(int i = 0; i < N; ++i) {
change(root, pos[i]);
if(pos[i] == len) { // 如果最大的数在最后,就没有比较的意义了。查询反而会使线段树结构出错
continue;
}
k += query(root, pos[i] + 1, len + 1); // 插入后,立刻进行累加,在线的
}

先不考虑离散化,离散化仅仅是缩小线段树的大小以及减少随之产生的查询次数,我们直接将离散后的pos当做原先说明的nums序列。
我们按照顺序迭代序列线段树求逆序对的详情理解_线段树_06,此时在线段树中插入当前数字线段树求逆序对的详情理解_逆序对_07(见代码第3行),随后便查询(pos[i] + 1, len + 1)的序列sum(见代码第7行)。
为什么查询(pos[i] + 1, len + 1)的序列sum呢?因为此时** (pos[i] + 1, len + 1)的序列sum值 是在之前已经进行插入的,且(pos[i] + 1, len + 1)保证了这里面的值肯定是大于当前线段树求逆序对的详情理解_逆序对_07的。
(pos[i] + 1, len + 1)的序列sum值**满足两个条件:

  1. 出现的数字所对应的下标小于线段树求逆序对的详情理解_逆序对_09
  2. 且当前所有的值线段树求逆序对的详情理解_leetcode_10

这样一思考意味着什么?即我们将当前插入线段树求逆序对的详情理解_线段树_11是为了后面判断是否有大于判断时所对应的数值,而query是为了统计大于线段树求逆序对的详情理解_逆序对_07的数值的个数。
换句话说就是,把线段树求逆序对的详情理解_逆序对_07理解为线段树求逆序对的详情理解_逆序对_05就好了,此时的线段树求逆序对的详情理解_线段树_11理解为定义的线段树求逆序对的详情理解_算法_16

注:这样就将一个统计大于对应的关系转换为一个区间查询的问题。

扩展思考

此处我们是以线段树求逆序对的详情理解_逆序对_05去进行思考的,我们能不能从线段树求逆序对的详情理解_逆序对_04的角度去思考,也就是统计其序列右侧小于它元素的个数?
答案肯定是可以的,考虑时效性。
也就是我们需要逆序遍历(为了让线段树求逆序对的详情理解_逆序对_04右边的数先都出现,并在线段树中进行标记嘛)
还有查询的是线段树求逆序对的详情理解_逆序对_20(因为我们要找比当前线段树求逆序对的详情理解_逆序对_04更小的数嘛)

逆序对扩展

在数列中只要有线段树求逆序对的详情理解_算法_22,且线段树求逆序对的详情理解_线段树_23,那么就称这是一个“好的”组合,给出任意个这个组合,求解“好的”组合的个数。
思路与逆序对一样,建树统计的代码也和逆序对的一样,区别在于统计方法上。

分析如下

// 处理逆序的,注意是反向循环的!!!!!!!
for(int i = N - 1; i >= 0; --i) {
change(root, pos[i]); // 将pos[i]的位置加一,顺便更新其父节点
if(pos[i] + 1 == len + 1) {
continue;
}
r[i] += query(root, pos[i] + 1, len + 1);
}
Node* root1{nullptr};
build(root1, 1, len + 1);
// 处理正序的
for(int i = 0; i < N; ++i) {
change(root1, pos[i]);
if(pos[i] <= 1) {
continue;
}
l[i] += query(root1, 1, pos[i]);
}

通过对逆序对扩展思考的理解,我们可以明白。逆序对扩展我们可以理解为统计序列中线段树求逆序对的详情理解_逆序对_04左侧小于其自身的数的个数线段树求逆序对的详情理解_逆序_25,以及右侧大于其自身数的个数线段树求逆序对的详情理解_算法_26
结合代码我们可以看出:
逆序时统计的是线段树求逆序对的详情理解_逆序对_27,则意味着求的是线段树求逆序对的详情理解_逆序_28,且线段树求逆序对的详情理解_算法_29这一段,找到所有数右侧大于它的部分,即线段树求逆序对的详情理解_算法_26
正序时统计的是线段树求逆序对的详情理解_算法_31,则意味着求的是线段树求逆序对的详情理解_leetcode_32线段树求逆序对的详情理解_算法_33这一段,找到所有数左侧小于它的部分,即线段树求逆序对的详情理解_逆序_25
然后结合这两个就可以了。

参考

​线段树之逆序对问题​

后记

笔者理解了很久,可能描述不是很清晰,可以随时来扰进行交流探讨。