计算数组中的反转

Posted

技术标签:

【中文标题】计算数组中的反转【英文标题】:Counting inversions in an array 【发布时间】:2010-09-25 03:48:14 【问题描述】:

我正在设计一种算法来执行以下操作:给定数组A[1... n],对于每个i < j,找到所有反转对,例如A[i] > A[j]。我正在使用合并排序并将数组 A 复制到数组 B,然后比较两个数组,但是我很难看到如何使用它来查找反转的数量。任何提示或帮助将不胜感激。

【问题讨论】:

【参考方案1】:

所以这里是 java 中的 O(n log n) 解决方案。

long merge(int[] arr, int[] left, int[] right) 
    int i = 0, j = 0, count = 0;
    while (i < left.length || j < right.length) 
        if (i == left.length) 
            arr[i+j] = right[j];
            j++;
         else if (j == right.length) 
            arr[i+j] = left[i];
            i++;
         else if (left[i] <= right[j]) 
            arr[i+j] = left[i];
            i++;                
         else 
            arr[i+j] = right[j];
            count += left.length-i;
            j++;
        
    
    return count;


long invCount(int[] arr) 
    if (arr.length < 2)
        return 0;

    int m = (arr.length + 1) / 2;
    int left[] = Arrays.copyOfRange(arr, 0, m);
    int right[] = Arrays.copyOfRange(arr, m, arr.length);

    return invCount(left) + invCount(right) + merge(arr, left, right);

这几乎是普通的归并排序,整个魔法隐藏在归并函数中。 请注意,虽然排序算法会删除反转。 合并算法计算删除倒置的数量(整理出来的人可能会说)。

唯一移除反转的时刻是算法从数组右侧获取元素并将其合并到主数组。 此操作删除的反转数是要合并的左侧数组中剩余的元素数。 :)

希望它足够解释。

【讨论】:

我尝试运行此程序,但没有得到正确答案。您是否应该在 main 中调用 invCount(intArray) 来开始? intArray 是未排序的 int 数组?我用一个包含许多整数的数组运行它,得到一个 -1887062008 作为我的答案。我做错了什么? +1,参见similar solution in C++11,包括一个通用的基于迭代器的解决方案和使用 5-25 个元素的序列的样本随机测试平台。享受吧! 这不是解决方案。我尝试运行它,但结果不正确。 抱歉这个新问题,但是将left.length - i 添加到反转计数器是怎么回事?我认为仅添加 1 是有意义的,因为您陷入了两个子数组之间的比较具有比右侧更大的左侧数组元素的逻辑情况。任何人都可以像我 5 岁一样向我解释吗? @AlfredoGallegos,简要说明了 Marek 的回答。考虑两个数组:[6, 8] 和 [4, 5]。当您看到 6 大于 4 时,您取 4 并将其放入 arr。但这不是一种倒置。您发现左侧数组中所有大于 6 的元素的反转。在我们的例子中,它也包括 8。所以,2 被添加到count,等于left.length - i【参考方案2】:

我通过以下方法在 O(n * log n) 时间内找到了它。

    合并排序数组 A 并创建一个副本(数组 B)

    取 A[1] 并通过二分搜索找到它在排序数组 B 中的位置。该元素的反转次数将比其在 B 中的位置的索引号少一个,因为出现在 A 的第一个元素之后的每个较低的数字都是反转。

    2a。将反转次数累积到计数器变量 num_inversions。

    2b。从数组 A 以及数组 B 中的相应位置删除 A[1]

    从第 2 步重新运行,直到 A 中没有更多元素。

这是该算法的一个示例运行。原始数组 A = (6, 9, 1, 14, 8, 12, 3, 2)

1:合并排序并复制到数组B

B = (1, 2, 3, 6, 8, 9, 12, 14)

2:在数组B中取A[1]并二分查找

A[1] = 6

B = (1, 2, 3, 6, 8, 9, 12, 14)

6 在数组 B 的第 4 位,因此有 3 次反转。我们知道这一点是因为 6 在数组 A 中的第一个位置,因此随后出现在数组 A 中的任何较低值元素的索引都将是 j > i(因为在这种情况下 i 是 1)。

2.b:从数组 A 中删除 A[1] 并从数组 B 中的相应位置删除(删除粗体元素)。

A = (6, 9, 1, 14, 8, 12, 3, 2) = (9, 1, 14, 8, 12, 3, 2)

B = (1, 2, 3, 6, 8, 9, 12, 14) = (1, 2, 3, 8, 9, 12, 14)

3:在新的 A 和 B 数组上从第 2 步重新运行。

A[1] = 9

B = (1, 2, 3, 8, 9, 12, 14)

9 现在位于数组 B 的第 5 位,因此有 4 次反转。我们知道这一点是因为 9 在数组 A 中的第一个位置,因此随后出现的任何较低值的元素都将具有 j > i 的索引(因为在这种情况下 i 又是 1)。 从数组 A 中删除 A[1] 并从数组 B 中的相应位置删除(粗体元素被删除)

A = (9, 1, 14, 8, 12, 3, 2) = (1, 14, 8, 12, 3, 2)

B = (1, 2, 3, 8, 9, 12, 14) = (1, 2, 3, 8, 12, 14)

在循环完成后,继续按照这种方式计算数组 A 的反转总数。

第 1 步(合并排序)需要 O(n * log n) 才能执行。 步骤 2 将执行 n 次,并且在每次执行时将执行二进制搜索,需要 O(log n) 来运行总共 O(n * log n)。因此,总运行时间为 O(n * log n) + O(n * log n) = O(n * log n)。

感谢您的帮助。在一张纸上写出示例数组确实有助于将问题可视化。

【讨论】:

为什么使用归并排序而不是快速排序? @Alcott 快速排序的最差运行时间为 O(n^2),此时列表已经排序,并且每轮都选择第一个枢轴。合并排序的最坏情况是 O(n log n) 从标准数组中删除步骤使您的算法 O(n^2),因为值发生了变化。 (这就是为什么插入排序是 O(n^2)) 从数组 B 的第一个元素开始计算数组 A 中它之前的元素也会得到相同的结果,前提是您按照答案中的描述消除它们。 @el diablo,好主意!但似乎除了所有删除操作的 O(n^2) 复杂度之外,还有一个问题。二进制搜索不搜索第一次出现。但我们需要第一个。考虑一个数组 [4, 7, 4]。您的方法将返回 2 个反转,而不是 1。更具体地说,在第一步中,二分搜索找到索引为 1 的“4”与索引为 0 的原始“4” - 我们得到 1 (=1-0) 错误反转。 【参考方案3】:

我想知道为什么还没有人提到binary-indexed trees。您可以使用 one 来维护排列元素值的前缀总和。然后你可以从右到左进行,并为每个元素计算小于右侧的元素数:

def count_inversions(a):
  res = 0
  counts = [0]*(len(a)+1)
  rank =  v : i+1 for i, v in enumerate(sorted(a)) 
  for x in reversed(a):
    i = rank[x] - 1
    while i:
      res += counts[i]
      i -= i & -i
    i = rank[x]
    while i <= len(a):
      counts[i] += 1
      i += i & -i
  return res

复杂度为O(n log n),常数因子很低。

【讨论】:

可能是最好的方法:) @NilutpalBorgohain 谢谢 :) 似乎至少在 O(n log n) 候选中需要最少的代码。 谢谢。 i -= i &amp; -i 行的含义是什么?同样i += i &amp; -i @GerardCondon 这基本上是 BIT 数据结构。可以在答案中找到解释它的链接 关于 Fenwick 树的信息。谢谢!我已经发布了an answer,它对这个问题的所有 Python 答案进行了timeit 比较,因此它包含了您的代码。您可能有兴趣查看计时结果。【参考方案4】:

在 Python 中

# O(n log n)

def count_inversion(lst):
    return merge_count_inversion(lst)[1]

def merge_count_inversion(lst):
    if len(lst) <= 1:
        return lst, 0
    middle = int( len(lst) / 2 )
    left, a = merge_count_inversion(lst[:middle])
    right, b = merge_count_inversion(lst[middle:])
    result, c = merge_count_split_inversion(left, right)
    return result, (a + b + c)

def merge_count_split_inversion(left, right):
    result = []
    count = 0
    i, j = 0, 0
    left_len = len(left)
    while i < left_len and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            count += left_len - i
            j += 1
    result += left[i:]
    result += right[j:]
    return result, count        


#test code
input_array_1 = []  #0
input_array_2 = [1] #0
input_array_3 = [1, 5]  #0
input_array_4 = [4, 1] #1
input_array_5 = [4, 1, 2, 3, 9] #3
input_array_6 = [4, 1, 3, 2, 9, 5]  #5
input_array_7 = [4, 1, 3, 2, 9, 1]  #8

print count_inversion(input_array_1)
print count_inversion(input_array_2)
print count_inversion(input_array_3)
print count_inversion(input_array_4)
print count_inversion(input_array_5)
print count_inversion(input_array_6)
print count_inversion(input_array_7)

【讨论】:

我对它是如何达到 +13 感到困惑 - 我对 Python 不是特别熟练,但它似乎与 the Java version presented 2 years before 几乎相同,除了这个 确实'不提供任何解释。以其他所有语言发布答案是非常有害的 IMO - 可能有数千种语言,如果不是更多的话 - 我希望没有人会争辩说我们应该发布数千个问题的答案 - Stack Exchange 不是为了那个。 @tennenrishin 好吧,也许不是数千。但是我们在哪里画线呢?目前,据我统计,10 个答案已经给出了相同的方法。这大约是 43% 的答案(不包括非答案) - 考虑到这里介绍了六种其他方法,需要占用相当多的空间。即使对于相同的方法只有 2 个答案,这仍然会不必要地稀释答案。我提出了一个相当不错的论点,这个答案特别在我之前的评论中没有用。 @Dukeling 和你一样,我不熟悉 Python,但更熟悉 Java。我发现这个解决方案比 Java 解决方案的可读性差得多。理所当然地,对于某些人来说,相反的情况可能在相同程度上是正确的。 @tennenrishin 我非常熟悉 Java,但我发现 高级解释比 Java 代码可读性高一百倍。如果交换了答案中的语言,我的回答可能是相同的(但如果它是第一个答案中的任何旧语言或任何旧语法,则不会 - 这两者都使用非常常见的语法,任何人都应该可以阅读体面的程序员,假设任何体面的程序员都会学习一种语法有点相似的语言)。 对于绝大多数用户来说,python 接近于 sudo 代码。老实说,即使它没有解释,我也发现它比 java 更具可读性。如果它对某些读者有所帮助,我认为没有必要如此生气。【参考方案5】:

实际上,我有一个与此类似的家庭作业问题。我被限制它必须具有 O(nlogn) 效率。

我使用了您提出的使用 Mergesort 的想法,因为它已经具有正确的效率。我只是在合并函数中插入了一些代码,基本上是: 每当将右侧数组中的一个数字添加到输出数组中时,我都会将反转的总数添加到左侧数组中剩余的数字数量。

这对我来说很有意义,因为我已经考虑得够多了。您计算在任何数字之前有多少次更大的数字。

hth.

【讨论】:

我支持你的回答,与合并排序的本质区别在于合并功能,当第二个右数组的元素被复制到输出数组时 => 将反转计数器增加第一个左数组中剩余的元素数【参考方案6】:

这个答案的主要目的是比较这里找到的各种 Python 版本的速度,但我也有一些自己的贡献。 (FWIW,我刚刚在执行重复搜索时发现了这个问题)。

在 CPython 中实现的算法的相对执行速度可能与人们对算法的简单分析以及使用其他语言的经验所期望的不同。这是因为 Python 提供了许多用 C 实现的强大函数和方法,它们可以以接近完全编译语言的速度对列表和其他集合进行操作,因此这些操作的运行速度比使用 Python“手动”实现的等效算法快得多代码。

利用这些工具的代码通常可以胜过理论上优秀的算法,这些算法试图通过 Python 操作对集合中的单个项目进行所有操作。当然,正在处理的实际数据量也会对此产生影响。但是对于中等数量的数据,使用以 C 速度运行的 O(n²) 算法的代码可以轻松击败使用单个 Python 操作完成大部分工作的 O(n log n) 算法。

此倒置计数问题的许多已发布答案都使用基于归并排序的算法。从理论上讲,这是一个很好的方法,除非数组大小非常小。但是 Python 内置的TimSort(一种混合稳定排序算法,源自归并排序和插入排序)以 C 的速度运行,Python 中手工编写的归并排序无法与它竞争速度。

这里有一个更有趣的解决方案,在 the answer posted by Niklas B 中,使用内置排序来确定数组项的排名,并使用 Binary Indexed Tree(又名 Fenwick 树)来存储计算反转所需的累积和数数。在试图理解这个数据结构和 Niklas 算法的过程中,我写了一些我自己的变体(贴在下面)。但我也发现,对于中等大小的列表,使用 Python 的内置 sum 函数实际上更快 比可爱的 Fenwick 树。

def count_inversions(a):
    total = 0
    counts = [0] * len(a)
    rank = v: i for i, v in enumerate(sorted(a))
    for u in reversed(a):
        i = rank[u]
        total += sum(counts[:i])
        counts[i] += 1
    return total

最终,当列表大小达到 500 左右时,在 for 循环内调用 sum 的 O(n²) 方面出现了丑陋的情况,性能开始下降。

Mergesort 不是唯一的 O(nlogn) 排序,还可以使用其他几种排序来执行倒数计数。 prasadvk's answer 使用二叉树排序,但他的代码似乎是 C++ 或其派生类之一。所以我添加了一个 Python 版本。我最初使用一个类来实现树节点,但发现 dict 明显更快。我最终使用了 list,它甚至更快,尽管它确实使代码的可读性降低了一些。

树排序的一个好处是它比合并排序更容易迭代实现。 Python 不优化递归并且它有一个递归深度限制(尽管如果你真的需要它可以增加它)。当然,Python 函数调用相对较慢,因此当您尝试优化速度时,最好在实际情况下避免函数调用。

另一种 O(nlogn) 排序是古老的基数排序。它的一大优点是它不会相互比较密钥。它的缺点是它在连续的整数序列上效果最好,理想情况下是range(b**m) 中的整数排列,其中b 通常为2。在尝试读取链接到的Counting Inversions, Offline Orthogonal Range Counting, and Related Problems 后,我添加了一些基于基数排序的版本calculating the number of “inversions” in a permutation.

为了有效地使用基数排序来计算长度为 n 的一般序列seq 中的反转,我们可以创建一个range(n) 的排列,它与seq 具有相同的反转数量。我们可以通过 TimSort 在(最坏的情况下)O(nlogn) 时间内做到这一点。诀窍是通过对seq 进行排序来置换seq 的索引。用一个小例子更容易解释。

seq = [15, 14, 11, 12, 10, 13]
b = [t[::-1] for t in enumerate(seq)]
print(b)
b.sort()
print(b)

输出

[(15, 0), (14, 1), (11, 2), (12, 3), (10, 4), (13, 5)]
[(10, 4), (11, 2), (12, 3), (13, 5), (14, 1), (15, 0)]

通过对seq 的(值,索引)对进行排序,我们用与将seq 从其排序顺序放入其原始顺序所需的相同数量的交换置换了seq 的索引。我们可以通过使用合适的键函数对range(n) 进行排序来创建这种排列:

print(sorted(range(len(seq)), key=lambda k: seq[k]))

输出

[4, 2, 3, 5, 1, 0]

我们可以通过使用seq.__getitem__ 方法来避免lambda

sorted(range(len(seq)), key=seq.__getitem__)

这只是稍微快一点,但我们正在寻找我们可以获得的所有速度增强功能。 ;)


下面的代码对该页面上所有现有的 Python 算法以及我自己的一些算法执行timeit 测试:几个蛮力 O(n²) 版本,Niklas B 算法的一些变体,以及当然是基于mergesort(我在没有参考现有答案的情况下写的)。它还有我的基于列表的树排序代码,大致源自 prasadvk 的代码,以及基于基数排序的各种函数,有些使用与合并排序方法类似的策略,有些使用sum 或 Fenwick 树。

这个程序在一系列随机整数列表上测量每个函数的执行时间;它还可以验证每个函数给出的结果是否与其他函数相同,并且它不会修改输入列表。

每个timeit 调用都会给出一个包含3 个结果的向量,我对其进行排序。此处要查看的主要值是最小值,其他值仅表明该最小值的可靠性,如the timeit module docs 中的注释中所述。

不幸的是,该程序的输出太大而无法包含在此答案中,因此我将其发布在its own (community wiki) answer。

输出来自我在旧的 Debian 衍生发行版上运行 Python 3.6.0 的古老 32 位单核 2GHz 机器上的 3 次运行。 YMMV。在测试期间,我关闭了 Web 浏览器并断开了与路由器的连接,以尽量减少其他任务对 CPU 的影响。

第一次运行测试列表大小从 5 到 320 的所有函数,循环大小从 4096 到 64(随着列表大小加倍,循环大小减半)。用于构造每个列表的随机池是列表本身大小的一半,因此我们可能会得到 lots 的重复项。一些反转计数算法比其他算法对重复更敏感。

第二次运行使用更大的列表:640 到 10240,固定循环大小为 8。为了节省时间,它从测试中删除了几个最慢的函数。我的蛮力 O(n²) 函数在这些大小上方式太慢了,如前所述,我使用 sum 的代码在中小型列表上表现良好,只是可以不要跟上大名单。

最终运行涵盖从 20480 到 655360 的列表大小,以及 4 的固定循环大小,以及 8 个最快的函数。对于 40,000 左右的列表大小,Tim Babych 的代码显然是赢家。干得好蒂姆! Niklas B 的代码也是一个很好的全能表现者,尽管它在较小的列表中被击败。 “python”的基于二分法的代码也做得相当好,虽然它看起来有点慢,有大量重复的列表,可能是由于它使用线性 while 循环来跳过欺骗。

但是,对于非常大的列表大小,基于二等分的算法无法与真正的 O(nlogn) 算法竞争。

#!/usr/bin/env python3

''' Test speeds of various ways of counting inversions in a list

    The inversion count is a measure of how sorted an array is.
    A pair of items in a are inverted if i < j but a[j] > a[i]

    See https://***.com/questions/337664/counting-inversions-in-an-array

    This program contains code by the following authors:
    mkso
    Niklas B
    B. M.
    Tim Babych
    python
    Zhe Hu
    prasadvk
    noman pouigt
    PM 2Ring

    Timing and verification code by PM 2Ring
    Collated 2017.12.16
    Updated 2017.12.21
'''

from timeit import Timer
from random import seed, randrange
from bisect import bisect, insort_left

seed('A random seed string')

# Merge sort version by mkso
def count_inversion_mkso(lst):
    return merge_count_inversion(lst)[1]

def merge_count_inversion(lst):
    if len(lst) <= 1:
        return lst, 0
    middle = len(lst) // 2
    left, a = merge_count_inversion(lst[:middle])
    right, b = merge_count_inversion(lst[middle:])
    result, c = merge_count_split_inversion(left, right)
    return result, (a + b + c)

def merge_count_split_inversion(left, right):
    result = []
    count = 0
    i, j = 0, 0
    left_len = len(left)
    while i < left_len and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            count += left_len - i
            j += 1
    result += left[i:]
    result += right[j:]
    return result, count

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# Using a Binary Indexed Tree, aka a Fenwick tree, by Niklas B.
def count_inversions_NiklasB(a):
    res = 0
    counts = [0] * (len(a) + 1)
    rank = v: i for i, v in enumerate(sorted(a), 1)
    for x in reversed(a):
        i = rank[x] - 1
        while i:
            res += counts[i]
            i -= i & -i
        i = rank[x]
        while i <= len(a):
            counts[i] += 1
            i += i & -i
    return res

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# Merge sort version by B.M
# Modified by PM 2Ring to deal with the global counter
bm_count = 0

def merge_count_BM(seq):
    global bm_count
    bm_count = 0
    sort_bm(seq)
    return bm_count

def merge_bm(l1,l2):
    global bm_count
    l = []
    while l1 and l2:
        if l1[-1] <= l2[-1]:
            l.append(l2.pop())
        else:
            l.append(l1.pop())
            bm_count += len(l2)
    l.reverse()
    return l1 + l2 + l

def sort_bm(l):
    t = len(l) // 2
    return merge_bm(sort_bm(l[:t]), sort_bm(l[t:])) if t > 0 else l

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# Bisection based method by Tim Babych
def solution_TimBabych(A):
    sorted_left = []
    res = 0
    for i in range(1, len(A)):
        insort_left(sorted_left, A[i-1])
        # i is also the length of sorted_left
        res += (i - bisect(sorted_left, A[i]))
    return res

# Slightly faster, except for very small lists
def solutionE_TimBabych(A):
    res = 0
    sorted_left = []
    for i, u in enumerate(A):
        # i is also the length of sorted_left
        res += (i - bisect(sorted_left, u))
        insort_left(sorted_left, u)
    return res

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# Bisection based method by "python"
def solution_python(A):
    B = list(A)
    B.sort()
    inversion_count = 0
    for i in range(len(A)):
        j = binarySearch_python(B, A[i])
        while B[j] == B[j - 1]:
            if j < 1:
                break
            j -= 1
        inversion_count += j
        B.pop(j)
    return inversion_count

def binarySearch_python(alist, item):
    first = 0
    last = len(alist) - 1
    found = False
    while first <= last and not found:
        midpoint = (first + last) // 2
        if alist[midpoint] == item:
            return midpoint
        else:
            if item < alist[midpoint]:
                last = midpoint - 1
            else:
                first = midpoint + 1

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# Merge sort version by Zhe Hu
def inv_cnt_ZheHu(a):
    _, count = inv_cnt(a.copy())
    return count

def inv_cnt(a):
    n = len(a)
    if n==1:
        return a, 0
    left = a[0:n//2] # should be smaller
    left, cnt1 = inv_cnt(left)
    right = a[n//2:] # should be larger
    right, cnt2 = inv_cnt(right)

    cnt = 0
    i_left = i_right = i_a = 0
    while i_a < n:
        if (i_right>=len(right)) or (i_left < len(left)
            and left[i_left] <= right[i_right]):
            a[i_a] = left[i_left]
            i_left += 1
        else:
            a[i_a] = right[i_right]
            i_right += 1
            if i_left < len(left):
                cnt += len(left) - i_left
        i_a += 1
    return (a, cnt1 + cnt2 + cnt)

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# Merge sort version by noman pouigt
# From https://***.com/q/47830098
def reversePairs_nomanpouigt(nums):
    def merge(left, right):
        if not left or not right:
            return (0, left + right)
        #if everything in left is less than right
        if left[len(left)-1] < right[0]:
            return (0, left + right)
        else:
            left_idx, right_idx, count = 0, 0, 0
            merged_output = []

            # check for condition before we merge it
            while left_idx < len(left) and right_idx < len(right):
                #if left[left_idx] > 2 * right[right_idx]:
                if left[left_idx] > right[right_idx]:
                    count += len(left) - left_idx
                    right_idx += 1
                else:
                    left_idx += 1

            #merging the sorted list
            left_idx, right_idx = 0, 0
            while left_idx < len(left) and right_idx < len(right):
                if left[left_idx] > right[right_idx]:
                    merged_output += [right[right_idx]]
                    right_idx += 1
                else:
                    merged_output += [left[left_idx]]
                    left_idx += 1
            if left_idx == len(left):
                merged_output += right[right_idx:]
            else:
                merged_output += left[left_idx:]
        return (count, merged_output)

    def partition(nums):
        count = 0
        if len(nums) == 1 or not nums:
            return (0, nums)
        pivot = len(nums)//2
        left_count, l = partition(nums[:pivot])
        right_count, r = partition(nums[pivot:])
        temp_count, temp_list = merge(l, r)
        return (temp_count + left_count + right_count, temp_list)
    return partition(nums)[0]

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# PM 2Ring
def merge_PM2R(seq):
    seq, count = merge_sort_count_PM2R(seq)
    return count

def merge_sort_count_PM2R(seq):
    mid = len(seq) // 2
    if mid == 0:
        return seq, 0
    left, left_total = merge_sort_count_PM2R(seq[:mid])
    right, right_total = merge_sort_count_PM2R(seq[mid:])
    total = left_total + right_total
    result = []
    i = j = 0
    left_len, right_len = len(left), len(right)
    while i < left_len and j < right_len:
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
            total += left_len - i
    result.extend(left[i:])
    result.extend(right[j:])
    return result, total

def rank_sum_PM2R(a):
    total = 0
    counts = [0] * len(a)
    rank = v: i for i, v in enumerate(sorted(a))
    for u in reversed(a):
        i = rank[u]
        total += sum(counts[:i])
        counts[i] += 1
    return total

# Fenwick tree functions adapted from C code on Wikipedia
def fen_sum(tree, i):
    ''' Return the sum of the first i elements, 0 through i-1 '''
    total = 0
    while i:
        total += tree[i-1]
        i -= i & -i
    return total

def fen_add(tree, delta, i):
    ''' Add delta to element i and thus 
        to fen_sum(tree, j) for all j > i 
    '''
    size = len(tree)
    while i < size:
        tree[i] += delta
        i += (i+1) & -(i+1)

def fenwick_PM2R(a):
    total = 0
    counts = [0] * len(a)
    rank = v: i for i, v in enumerate(sorted(a))
    for u in reversed(a):
        i = rank[u]
        total += fen_sum(counts, i)
        fen_add(counts, 1, i)
    return total

def fenwick_inline_PM2R(a):
    total = 0
    size = len(a)
    counts = [0] * size
    rank = v: i for i, v in enumerate(sorted(a))
    for u in reversed(a):
        i = rank[u]
        j = i + 1
        while i:
            total += counts[i]
            i -= i & -i
        while j < size:
            counts[j] += 1
            j += j & -j
    return total

def bruteforce_loops_PM2R(a):
    total = 0
    for i in range(1, len(a)):
        u = a[i]
        for j in range(i):
            if a[j] > u:
                total += 1
    return total

def bruteforce_sum_PM2R(a):
    return sum(1 for i in range(1, len(a)) for j in range(i) if a[j] > a[i])

# Using binary tree counting, derived from C++ code (?) by prasadvk
# https://***.com/a/16056139
def ltree_count_PM2R(a):
    total, root = 0, None
    for u in a:
        # Store data in a list-based tree structure
        # [data, count, left_child, right_child]
        p = [u, 0, None, None]
        if root is None:
            root = p
            continue
        q = root
        while True:
            if p[0] < q[0]:
                total += 1 + q[1]
                child = 2
            else:
                q[1] += 1
                child = 3
            if q[child]:
                q = q[child]
            else:
                q[child] = p
                break
    return total

# Counting based on radix sort, recursive version
def radix_partition_rec(a, L):
    if len(a) < 2:
        return 0
    if len(a) == 2:
        return a[1] < a[0]
    left, right = [], []
    count = 0
    for u in a:
        if u & L:
            right.append(u)
        else:
            count += len(right)
            left.append(u)
    L >>= 1
    if L:
        count += radix_partition_rec(left, L) + radix_partition_rec(right, L)
    return count

# The following functions determine swaps using a permutation of 
# range(len(a)) that has the same inversion count as `a`. We can create
# this permutation with `sorted(range(len(a)), key=lambda k: a[k])`
# but `sorted(range(len(a)), key=a.__getitem__)` is a little faster.

# Counting based on radix sort, iterative version
def radix_partition_iter(seq, L):
    count = 0
    parts = [seq]
    while L and parts:
        newparts = []
        for a in parts:
            if len(a) < 2:
                continue
            if len(a) == 2:
                count += a[1] < a[0]
                continue
            left, right = [], []
            for u in a:
                if u & L:
                    right.append(u)
                else:
                    count += len(right)
                    left.append(u)
            if left:
                newparts.append(left)
            if right:
                newparts.append(right)
        parts = newparts
        L >>= 1
    return count

def perm_radixR_PM2R(a):
    size = len(a)
    b = sorted(range(size), key=a.__getitem__)
    n = size.bit_length() - 1
    return radix_partition_rec(b, 1 << n)

def perm_radixI_PM2R(a):
    size = len(a)
    b = sorted(range(size), key=a.__getitem__)
    n = size.bit_length() - 1
    return radix_partition_iter(b, 1 << n)

# Plain sum of the counts of the permutation
def perm_sum_PM2R(a):
    total = 0
    size = len(a)
    counts = [0] * size
    for i in reversed(sorted(range(size), key=a.__getitem__)):
        total += sum(counts[:i])
        counts[i] = 1
    return total

# Fenwick sum of the counts of the permutation
def perm_fenwick_PM2R(a):
    total = 0
    size = len(a)
    counts = [0] * size
    for i in reversed(sorted(range(size), key=a.__getitem__)):
        j = i + 1
        while i:
            total += counts[i]
            i -= i & -i
        while j < size:
            counts[j] += 1
            j += j & -j
    return total

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# All the inversion-counting functions
funcs = (
    solution_TimBabych,
    solutionE_TimBabych,
    solution_python,
    count_inversion_mkso,
    count_inversions_NiklasB,
    merge_count_BM,
    inv_cnt_ZheHu,
    reversePairs_nomanpouigt,
    fenwick_PM2R,
    fenwick_inline_PM2R,
    merge_PM2R,
    rank_sum_PM2R,
    bruteforce_loops_PM2R,
    bruteforce_sum_PM2R,
    ltree_count_PM2R,
    perm_radixR_PM2R,
    perm_radixI_PM2R,
    perm_sum_PM2R,
    perm_fenwick_PM2R,
)

def time_test(seq, loops, verify=False):
    orig = seq
    timings = []
    for func in funcs:
        seq = orig.copy()
        value = func(seq) if verify else None
        t = Timer(lambda: func(seq))
        result = sorted(t.repeat(3, loops))
        timings.append((result, func.__name__, value))
        assert seq==orig, 'Sequence altered by !'.format(func.__name__)
    first = timings[0][-1]
    timings.sort()
    for result, name, value in timings:
        result = ', '.join([format(u, '.5f') for u in result])
        print(':24 : '.format(name, result))

    if verify:
        # Check that all results are identical
        bad = ['%s: %d' % (name, value)
            for _, name, value in timings if value != first]
        if bad:
            print('ERROR. Value: , bad: '.format(first, ', '.join(bad)))
        else:
            print('Value: '.format(first))
    print()

#Run the tests
size, loops = 5, 1 << 12
verify = True
for _ in range(7):
    hi = size // 2
    print('Size = , hi = ,  loops'.format(size, hi, loops))
    seq = [randrange(hi) for _ in range(size)]
    time_test(seq, loops, verify)
    loops >>= 1
    size <<= 1

#size, loops = 640, 8
#verify = False
#for _ in range(5):
    #hi = size // 2
    #print('Size = , hi = ,  loops'.format(size, hi, loops))
    #seq = [randrange(hi) for _ in range(size)]
    #time_test(seq, loops, verify)
    #size <<= 1

#size, loops = 163840, 4
#verify = False
#for _ in range(3):
    #hi = size // 2
    #print('Size = , hi = ,  loops'.format(size, hi, loops))
    #seq = [randrange(hi) for _ in range(size)]
    #time_test(seq, loops, verify)
    #size <<= 1

Please see here for the output

【讨论】:

谢谢,这很有趣 :) 清楚地展示了使用 C 模块的好处 - bisect 是。 问题在于获胜者使用(理论上)二次算法。对于大小〜100 000,它会被其他人殴打。我编辑了我的帖子以放置一个 python 准线性 C 速度解决方案。 @B.M.当然,但是 Tim 的平分方法非常好,直到您达到 45,000 左右的大小。我还有一些解决方案,我会在第二天左右添加到这里。 @TimBabych 你是说bisect 是C 吗?我很确定它是 Python。 Python 的 bisect 模块是用 C 编写的,见github.com/python/cpython/blob/master/Modules/_bisectmodule.cgithub.com/python/cpython/blob/master/Lib/bisect.py#L84【参考方案7】:

通过分析归并排序中的归并过程可以找到倒数的次数:

当将元素从第二个数组复制到合并数组(本例中的 9)时,它相对于其他元素保持其位置。当将一个元素从第一个数组复制到合并数组(这里是 5)时,它会被反转,所有元素都留在第二个数组中(3 和 4 的 2 次反转)。所以对归并排序稍加修改就可以解决 O(n ln n) 的问题。 例如,只需取消注释下面的 mergesort python 代码中的两行 # 即可获得计数。

def merge(l1,l2):
    l = []
    # global count
    while l1 and l2:
        if l1[-1] <= l2[-1]:
            l.append(l2.pop())
        else:
            l.append(l1.pop())
            # count += len(l2)
    l.reverse()
    return l1 + l2 + l

def sort(l): 
    t = len(l) // 2
    return merge(sort(l[:t]), sort(l[t:])) if t > 0 else l

count=0
print(sort([5,1,2,4,9,3]), count)
# [1, 2, 3, 4, 5, 9] 6

编辑 1

使用稳定版本的快速排序也可以完成相同的任务,已知速度稍快:

def part(l):
    pivot=l[-1]
    small,big = [],[]
    count = big_count = 0
    for x in l:
        if x <= pivot:
            small.append(x)
            count += big_count
        else:
            big.append(x)
            big_count += 1
    return count,small,big

def quick_count(l):
    if len(l)<2 : return 0
    count,small,big = part(l)
    small.pop()
    return count + quick_count(small) + quick_count(big)

选择枢轴作为最后一个元素,反转计算得当,执行时间比上面的合并快 40%。

编辑 2

为了在 python 中的性能,一个 numpy 和 numba 版本:

首先是 numpy 部分,它使用 argsort O (n ln n) :

def count_inversions(a):
    n = a.size
    counts = np.arange(n) & -np.arange(n)  # The BIT
    ags = a.argsort(kind='mergesort')    
    return  BIT(ags,counts,n)

高效的BIT approach 的 numba 部分:

@numba.njit
def BIT(ags,counts,n):
    res = 0        
    for x in ags :
        i = x
        while i:
            res += counts[i]
            i -= i & -i
        i = x+1
        while i < n:
            counts[i] -= 1
            i += i & -i
    return  res  

【讨论】:

我已经发布了an answer,它对这个问题的所有 Python 答案进行了timeit 比较,因此它包含您的代码。您可能有兴趣查看计时结果。 这篇文章没有性能问题...我会在一段时间内尝试。 numpy numba 允许 ;) ? 我从来没有用过 Numba,但我用过一点 Numpy,并想自己添加一个 Numpy 版本,但我决定只将测试限制在只使用标准库的解决方案上。但我想看看 Numpy 解决方案的比较会很有趣。我怀疑在小列表上它不会更快。 100 倍的加速令人印象深刻!但我无法运行它,因为我没有 Numba。正如我之前所说,将它包含在我的 timeit 收藏中是不公平的。【参考方案8】:

请注意,杰弗里欧文的回答是错误的。

数组中的反转次数是元素必须移动的总距离的一半才能对数组进行排序。因此,它可以通过对数组进行排序,保持结果排列 p[i],然后计算 abs(p[i]-i)/2 的和来计算。这需要 O(n log n) 时间,这是最优的。

http://mathworld.wolfram.com/PermutationInversion.html 提供了另一种方法。该方法等价于 max(0, p[i]-i) 之和,它等于 abs(p[i]-i])/2 之和,因为元素向左移动的总距离等于总距离元素向右移动。

以序列 3, 2, 1 为例。有三个反转:(3, 2), (3, 1), (2, 1),所以反转数是 3。但是,根据引用的方法,答案应该是 2。

【讨论】:

正确答案可以通过计算相邻交换的最小所需数量来找到。查看讨论:***.com/questions/20990127/…【参考方案9】:

看看这个:http://www.cs.jhu.edu/~xfliu/600.363_F03/hw_solution/solution1.pdf

希望能给你正确的答案。

2-3 反演部分(d) 运行时间为 O(nlogn)

【讨论】:

【参考方案10】:

这是一种可能的二叉树变体解决方案。它将一个名为 rightSubTreeSize 的字段添加到每个树节点。继续按照它们在数组中出现的顺序将数字插入二叉树。如果 number 为节点的 lhs,则该元素的反转计数将为 (1 + rightSubTreeSize)。由于所有这些元素都大于当前元素,并且它们会更早地出现在数组中。如果元素去到一个节点的rhs,只需增加它的rightSubTreeSize。以下是代码。

Node  
    int data;
    Node* left, *right;
    int rightSubTreeSize;

    Node(int data)  
        rightSubTreeSize = 0;
       
;

Node* root = null;
int totCnt = 0;
for(i = 0; i < n; ++i)  
    Node* p = new Node(a[i]);
    if(root == null)  
        root = p;
        continue;
     

    Node* q = root;
    int curCnt = 0;
    while(q)  
        if(p->data <= q->data)  
            curCnt += 1 + q->rightSubTreeSize;
            if(q->left)  
                q = q->left;
             else  
                q->left = p;
                break;
            
         else  
            q->rightSubTreeSize++;
            if(q->right)  
                q = q->right;
             else  
                q->right = p;
                break;
            
        
    

    totCnt += curCnt;
  
  return totCnt;

【讨论】:

这是一种有趣的方法,而且看起来相当快。但是,该比较需要为if(p-&gt;data &lt; q-&gt;data),否则无法正确处理重复项。并且无需在循环顶部测试q,无条件的while 循环可以正常工作。另外,你忽略了这是什么语言。 :) 而且您的函数似乎丢失了标题行。 我刚刚在我的答案中添加了一个基于你的树算法的 Python 版本。当然,它不如完全编译的版本快,但相对于其他 Python 版本,它做得相当好。【参考方案11】:
public static int mergeSort(int[] a, int p, int r)

    int countInversion = 0;
    if(p < r)
    
        int q = (p + r)/2;
        countInversion = mergeSort(a, p, q);
        countInversion += mergeSort(a, q+1, r);
        countInversion += merge(a, p, q, r);
    
    return countInversion;


public static int merge(int[] a, int p, int q, int r)

    //p=0, q=1, r=3
    int countingInversion = 0;
    int n1 = q-p+1;
    int n2 = r-q;
    int[] temp1 = new int[n1+1];
    int[] temp2 = new int[n2+1];
    for(int i=0; i<n1; i++) temp1[i] = a[p+i];
    for(int i=0; i<n2; i++) temp2[i] = a[q+1+i];

    temp1[n1] = Integer.MAX_VALUE;
    temp2[n2] = Integer.MAX_VALUE;
    int i = 0, j = 0;

    for(int k=p; k<=r; k++)
    
        if(temp1[i] <= temp2[j])
        
            a[k] = temp1[i];
            i++;
        
        else
        
            a[k] = temp2[j];
            j++;
            countingInversion=countingInversion+(n1-i); 
        
    
    return countingInversion;

public static void main(String[] args)

    int[] a = 1, 20, 6, 4, 5;
    int countInversion = mergeSort(a, 0, a.length-1);
    System.out.println(countInversion);

【讨论】:

这与已经发布的Java 和Python 解决方案有很大不同吗?此外,仅代码的答案并不是特别好的 IMO,尤其是考虑到这个问题甚至没有指定语言。【参考方案12】:

此答案包含由我的main answer 中的代码生成的timeit 测试的结果。详情请查看该答案!

count_inversions speed test results

Size = 5, hi = 2, 4096 loops
ltree_count_PM2R         : 0.04871, 0.04872, 0.04876
bruteforce_loops_PM2R    : 0.05696, 0.05700, 0.05776
solution_TimBabych       : 0.05760, 0.05822, 0.05943
solutionE_TimBabych      : 0.06642, 0.06704, 0.06760
bruteforce_sum_PM2R      : 0.07523, 0.07545, 0.07563
perm_sum_PM2R            : 0.09873, 0.09875, 0.09935
rank_sum_PM2R            : 0.10449, 0.10463, 0.10468
solution_python          : 0.13034, 0.13061, 0.13221
fenwick_inline_PM2R      : 0.14323, 0.14610, 0.18802
perm_radixR_PM2R         : 0.15146, 0.15203, 0.15235
merge_count_BM           : 0.16179, 0.16267, 0.16467
perm_radixI_PM2R         : 0.16200, 0.16202, 0.16768
perm_fenwick_PM2R        : 0.16887, 0.16920, 0.17075
merge_PM2R               : 0.18262, 0.18271, 0.18418
count_inversions_NiklasB : 0.19183, 0.19279, 0.20388
count_inversion_mkso     : 0.20060, 0.20141, 0.20398
inv_cnt_ZheHu            : 0.20815, 0.20841, 0.20906
fenwick_PM2R             : 0.22109, 0.22137, 0.22379
reversePairs_nomanpouigt : 0.29620, 0.29689, 0.30293
Value: 5

Size = 10, hi = 5, 2048 loops
solution_TimBabych       : 0.05954, 0.05989, 0.05991
solutionE_TimBabych      : 0.05970, 0.05972, 0.05998
perm_sum_PM2R            : 0.07517, 0.07519, 0.07520
ltree_count_PM2R         : 0.07672, 0.07677, 0.07684
bruteforce_loops_PM2R    : 0.07719, 0.07724, 0.07817
rank_sum_PM2R            : 0.08587, 0.08823, 0.08864
bruteforce_sum_PM2R      : 0.09470, 0.09472, 0.09484
solution_python          : 0.13126, 0.13154, 0.13185
perm_radixR_PM2R         : 0.14239, 0.14320, 0.14474
perm_radixI_PM2R         : 0.14632, 0.14669, 0.14679
fenwick_inline_PM2R      : 0.16796, 0.16831, 0.17030
perm_fenwick_PM2R        : 0.18189, 0.18212, 0.18638
merge_count_BM           : 0.19816, 0.19870, 0.19948
count_inversions_NiklasB : 0.21807, 0.22031, 0.22215
merge_PM2R               : 0.22037, 0.22048, 0.26106
fenwick_PM2R             : 0.24290, 0.24314, 0.24744
count_inversion_mkso     : 0.24895, 0.24899, 0.25205
inv_cnt_ZheHu            : 0.26253, 0.26259, 0.26590
reversePairs_nomanpouigt : 0.35711, 0.35762, 0.35973
Value: 20

Size = 20, hi = 10, 1024 loops
solutionE_TimBabych      : 0.05687, 0.05696, 0.05720
solution_TimBabych       : 0.06126, 0.06151, 0.06168
perm_sum_PM2R            : 0.06875, 0.06906, 0.07054
rank_sum_PM2R            : 0.07988, 0.07995, 0.08002
ltree_count_PM2R         : 0.11232, 0.11239, 0.11257
bruteforce_loops_PM2R    : 0.12553, 0.12584, 0.12592
solution_python          : 0.13472, 0.13540, 0.13694
bruteforce_sum_PM2R      : 0.15820, 0.15849, 0.16021
perm_radixI_PM2R         : 0.17101, 0.17148, 0.17229
perm_radixR_PM2R         : 0.17891, 0.18087, 0.18366
perm_fenwick_PM2R        : 0.20554, 0.20708, 0.21412
fenwick_inline_PM2R      : 0.21161, 0.21163, 0.22047
merge_count_BM           : 0.24125, 0.24261, 0.24565
count_inversions_NiklasB : 0.25712, 0.25754, 0.25778
merge_PM2R               : 0.26477, 0.26566, 0.31297
fenwick_PM2R             : 0.28178, 0.28216, 0.29069
count_inversion_mkso     : 0.30286, 0.30290, 0.30652
inv_cnt_ZheHu            : 0.32024, 0.32041, 0.32447
reversePairs_nomanpouigt : 0.45812, 0.45822, 0.46172
Value: 98

Size = 40, hi = 20, 512 loops
solutionE_TimBabych      : 0.05784, 0.05787, 0.05958
solution_TimBabych       : 0.06452, 0.06475, 0.06479
perm_sum_PM2R            : 0.07254, 0.07261, 0.07263
rank_sum_PM2R            : 0.08537, 0.08540, 0.08572
ltree_count_PM2R         : 0.11744, 0.11749, 0.11792
solution_python          : 0.14262, 0.14285, 0.14465
perm_radixI_PM2R         : 0.18774, 0.18776, 0.18922
perm_radixR_PM2R         : 0.19425, 0.19435, 0.19609
bruteforce_loops_PM2R    : 0.21500, 0.21511, 0.21686
perm_fenwick_PM2R        : 0.23338, 0.23375, 0.23674
fenwick_inline_PM2R      : 0.24947, 0.24958, 0.25189
bruteforce_sum_PM2R      : 0.27627, 0.27646, 0.28041
merge_count_BM           : 0.28059, 0.28128, 0.28294
count_inversions_NiklasB : 0.28557, 0.28759, 0.29022
merge_PM2R               : 0.29886, 0.29928, 0.30317
fenwick_PM2R             : 0.30241, 0.30259, 0.35237
count_inversion_mkso     : 0.34252, 0.34356, 0.34441
inv_cnt_ZheHu            : 0.37468, 0.37569, 0.37847
reversePairs_nomanpouigt : 0.50725, 0.50770, 0.50943
Value: 369

Size = 80, hi = 40, 256 loops
solutionE_TimBabych      : 0.06339, 0.06373, 0.06513
solution_TimBabych       : 0.06984, 0.06994, 0.07009
perm_sum_PM2R            : 0.09171, 0.09172, 0.09186
rank_sum_PM2R            : 0.10468, 0.10474, 0.10500
ltree_count_PM2R         : 0.14416, 0.15187, 0.18541
solution_python          : 0.17415, 0.17423, 0.17451
perm_radixI_PM2R         : 0.20676, 0.20681, 0.20936
perm_radixR_PM2R         : 0.21671, 0.21695, 0.21736
perm_fenwick_PM2R        : 0.26197, 0.26252, 0.26264
fenwick_inline_PM2R      : 0.28111, 0.28249, 0.28382
count_inversions_NiklasB : 0.31746, 0.32448, 0.32451
merge_count_BM           : 0.31964, 0.33842, 0.35276
merge_PM2R               : 0.32890, 0.32941, 0.33322
fenwick_PM2R             : 0.34355, 0.34377, 0.34873
count_inversion_mkso     : 0.37689, 0.37698, 0.38079
inv_cnt_ZheHu            : 0.42923, 0.42941, 0.43249
bruteforce_loops_PM2R    : 0.43544, 0.43601, 0.43902
bruteforce_sum_PM2R      : 0.52106, 0.52160, 0.52531
reversePairs_nomanpouigt : 0.57805, 0.58156, 0.58252
Value: 1467

Size = 160, hi = 80, 128 loops
solutionE_TimBabych      : 0.06766, 0.06784, 0.06963
solution_TimBabych       : 0.07433, 0.07489, 0.07516
perm_sum_PM2R            : 0.13143, 0.13175, 0.13179
rank_sum_PM2R            : 0.14428, 0.14440, 0.14922
solution_python          : 0.20072, 0.20076, 0.20084
ltree_count_PM2R         : 0.20314, 0.20583, 0.24776
perm_radixI_PM2R         : 0.23061, 0.23078, 0.23525
perm_radixR_PM2R         : 0.23894, 0.23915, 0.24234
perm_fenwick_PM2R        : 0.30984, 0.31181, 0.31503
fenwick_inline_PM2R      : 0.31933, 0.32680, 0.32722
merge_count_BM           : 0.36003, 0.36387, 0.36409
count_inversions_NiklasB : 0.36796, 0.36814, 0.37106
merge_PM2R               : 0.36847, 0.36848, 0.37127
fenwick_PM2R             : 0.37833, 0.37847, 0.38095
count_inversion_mkso     : 0.42746, 0.42747, 0.43184
inv_cnt_ZheHu            : 0.48969, 0.48974, 0.49293
reversePairs_nomanpouigt : 0.67791, 0.68157, 0.72420
bruteforce_loops_PM2R    : 0.82816, 0.83175, 0.83282
bruteforce_sum_PM2R      : 1.03322, 1.03378, 1.03562
Value: 6194

Size = 320, hi = 160, 64 loops
solutionE_TimBabych      : 0.07467, 0.07470, 0.07483
solution_TimBabych       : 0.08036, 0.08066, 0.08077
perm_sum_PM2R            : 0.21142, 0.21201, 0.25766
solution_python          : 0.22410, 0.22644, 0.22897
rank_sum_PM2R            : 0.22820, 0.22851, 0.22877
ltree_count_PM2R         : 0.24424, 0.24595, 0.24645
perm_radixI_PM2R         : 0.25690, 0.25710, 0.26191
perm_radixR_PM2R         : 0.26501, 0.26504, 0.26729
perm_fenwick_PM2R        : 0.33483, 0.33507, 0.33845
fenwick_inline_PM2R      : 0.34413, 0.34484, 0.35153
merge_count_BM           : 0.39875, 0.39919, 0.40302
fenwick_PM2R             : 0.40434, 0.40439, 0.40845
merge_PM2R               : 0.40814, 0.41531, 0.51417
count_inversions_NiklasB : 0.41681, 0.42009, 0.42128
count_inversion_mkso     : 0.47132, 0.47192, 0.47385
inv_cnt_ZheHu            : 0.54468, 0.54750, 0.54893
reversePairs_nomanpouigt : 0.76164, 0.76389, 0.80357
bruteforce_loops_PM2R    : 1.59125, 1.60430, 1.64131
bruteforce_sum_PM2R      : 2.03734, 2.03834, 2.03975
Value: 24959

Run 2

Size = 640, hi = 320, 8 loops
solutionE_TimBabych      : 0.04135, 0.04374, 0.04575
ltree_count_PM2R         : 0.06738, 0.06758, 0.06874
perm_radixI_PM2R         : 0.06928, 0.06943, 0.07019
fenwick_inline_PM2R      : 0.07850, 0.07856, 0.08059
perm_fenwick_PM2R        : 0.08151, 0.08162, 0.08170
perm_sum_PM2R            : 0.09122, 0.09133, 0.09221
rank_sum_PM2R            : 0.09549, 0.09603, 0.11270
merge_count_BM           : 0.10733, 0.10807, 0.11032
count_inversions_NiklasB : 0.12460, 0.19865, 0.20205
solution_python          : 0.13514, 0.13585, 0.13814

Size = 1280, hi = 640, 8 loops
solutionE_TimBabych      : 0.04714, 0.04742, 0.04752
perm_radixI_PM2R         : 0.15325, 0.15388, 0.15525
solution_python          : 0.15709, 0.15715, 0.16076
fenwick_inline_PM2R      : 0.16048, 0.16160, 0.16403
ltree_count_PM2R         : 0.16213, 0.16238, 0.16428
perm_fenwick_PM2R        : 0.16408, 0.16416, 0.16449
count_inversions_NiklasB : 0.19755, 0.19833, 0.19897
merge_count_BM           : 0.23736, 0.23793, 0.23912
perm_sum_PM2R            : 0.32946, 0.32969, 0.33277
rank_sum_PM2R            : 0.34637, 0.34756, 0.34858

Size = 2560, hi = 1280, 8 loops
solutionE_TimBabych      : 0.10898, 0.11005, 0.11025
perm_radixI_PM2R         : 0.33345, 0.33352, 0.37656
ltree_count_PM2R         : 0.34670, 0.34786, 0.34833
perm_fenwick_PM2R        : 0.34816, 0.34879, 0.35214
fenwick_inline_PM2R      : 0.36196, 0.36455, 0.36741
solution_python          : 0.36498, 0.36637, 0.40887
count_inversions_NiklasB : 0.42274, 0.42745, 0.42995
merge_count_BM           : 0.50799, 0.50898, 0.50917
perm_sum_PM2R            : 1.27773, 1.27897, 1.27951
rank_sum_PM2R            : 1.29728, 1.30389, 1.30448

Size = 5120, hi = 2560, 8 loops
solutionE_TimBabych      : 0.26914, 0.26993, 0.27253
perm_radixI_PM2R         : 0.71416, 0.71634, 0.71753
perm_fenwick_PM2R        : 0.71976, 0.72078, 0.72078
fenwick_inline_PM2R      : 0.72776, 0.72804, 0.73143
ltree_count_PM2R         : 0.81972, 0.82043, 0.82290
solution_python          : 0.83714, 0.83756, 0.83962
count_inversions_NiklasB : 0.87282, 0.87395, 0.92087
merge_count_BM           : 1.09496, 1.09584, 1.10207
rank_sum_PM2R            : 5.02564, 5.06277, 5.06666
perm_sum_PM2R            : 5.09088, 5.12999, 5.13512

Size = 10240, hi = 5120, 8 loops
solutionE_TimBabych      : 0.71556, 0.71718, 0.72201
perm_radixI_PM2R         : 1.54785, 1.55096, 1.55515
perm_fenwick_PM2R        : 1.55103, 1.55353, 1.59298
fenwick_inline_PM2R      : 1.57118, 1.57240, 1.57271
ltree_count_PM2R         : 1.76240, 1.76247, 1.80944
count_inversions_NiklasB : 1.86543, 1.86851, 1.87208
solution_python          : 2.01490, 2.01519, 2.06423
merge_count_BM           : 2.35215, 2.35301, 2.40023
rank_sum_PM2R            : 20.07048, 20.08399, 20.13200
perm_sum_PM2R            : 20.10187, 20.12551, 20.12683

Run 3
Size = 20480, hi = 10240, 4 loops
solutionE_TimBabych      : 1.07636, 1.08243, 1.09569
perm_radixI_PM2R         : 1.59579, 1.60519, 1.61785
perm_fenwick_PM2R        : 1.66885, 1.68549, 1.71109
fenwick_inline_PM2R      : 1.72073, 1.72752, 1.77217
ltree_count_PM2R         : 1.96900, 1.97820, 2.02578
count_inversions_NiklasB : 2.03257, 2.05005, 2.18548
merge_count_BM           : 2.46768, 2.47377, 2.52133
solution_python          : 2.49833, 2.50179, 3.79819

Size = 40960, hi = 20480, 4 loops
solutionE_TimBabych      : 3.51733, 3.52008, 3.56996
perm_radixI_PM2R         : 3.51736, 3.52365, 3.56459
perm_fenwick_PM2R        : 3.76097, 3.80900, 3.87974
fenwick_inline_PM2R      : 3.95099, 3.96300, 3.99748
ltree_count_PM2R         : 4.49866, 4.54652, 5.39716
count_inversions_NiklasB : 4.61851, 4.64303, 4.73026
merge_count_BM           : 5.31945, 5.35378, 5.35951
solution_python          : 6.78756, 6.82911, 6.98217

Size = 81920, hi = 40960, 4 loops
perm_radixI_PM2R         : 7.68723, 7.71986, 7.72135
perm_fenwick_PM2R        : 8.52404, 8.53349, 8.53710
fenwick_inline_PM2R      : 8.97082, 8.97561, 8.98347
ltree_count_PM2R         : 10.01142, 10.01426, 10.03216
count_inversions_NiklasB : 10.60807, 10.62424, 10.70425
merge_count_BM           : 11.42149, 11.42342, 11.47003
solutionE_TimBabych      : 12.83390, 12.83485, 12.89747
solution_python          : 19.66092, 19.67067, 20.72204

Size = 163840, hi = 81920, 4 loops
perm_radixI_PM2R         : 17.14153, 17.16885, 17.22240
perm_fenwick_PM2R        : 19.25944, 19.27844, 20.27568
fenwick_inline_PM2R      : 19.78221, 19.80219, 19.80766
ltree_count_PM2R         : 22.42240, 22.43259, 22.48837
count_inversions_NiklasB : 22.97341, 23.01516, 23.98052
merge_count_BM           : 24.42683, 24.48559, 24.51488
solutionE_TimBabych      : 60.96006, 61.20145, 63.71835
solution_python          : 73.75132, 73.79854, 73.95874

Size = 327680, hi = 163840, 4 loops
perm_radixI_PM2R         : 36.56715, 36.60221, 37.05071
perm_fenwick_PM2R        : 42.21616, 42.21838, 42.26053
fenwick_inline_PM2R      : 43.04987, 43.09075, 43.13287
ltree_count_PM2R         : 49.87400, 50.08509, 50.69292
count_inversions_NiklasB : 50.74591, 50.75012, 50.75551
merge_count_BM           : 52.37284, 52.51491, 53.43003
solutionE_TimBabych      : 373.67198, 377.03341, 377.42360
solution_python          : 411.69178, 411.92691, 412.83856

Size = 655360, hi = 327680, 4 loops
perm_radixI_PM2R         : 78.51927, 78.66327, 79.46325
perm_fenwick_PM2R        : 90.64711, 90.80328, 91.76126
fenwick_inline_PM2R      : 93.32482, 93.39086, 94.28880
count_inversions_NiklasB : 107.74393, 107.80036, 108.71443
ltree_count_PM2R         : 109.11328, 109.23592, 110.18247
merge_count_BM           : 111.05633, 111.07840, 112.05861
solutionE_TimBabych      : 1830.46443, 1836.39960, 1849.53918
solution_python          : 1911.03692, 1912.04484, 1914.69786

【讨论】:

【参考方案13】:

由于这是一个老问题,我将在 C 中提供我的答案。

#include <stdio.h>

int count = 0;
int inversions(int a[], int len);
void mergesort(int a[], int left, int right);
void merge(int a[], int left, int mid, int right);

int main() 
  int a[] =  1, 5, 2, 4, 0 ;
  printf("%d\n", inversions(a, 5));


int inversions(int a[], int len) 
  mergesort(a, 0, len - 1);
  return count;


void mergesort(int a[], int left, int right) 
  if (left < right) 
     int mid = (left + right) / 2;
     mergesort(a, left, mid);
     mergesort(a, mid + 1, right);
     merge(a, left, mid, right);
  


void merge(int a[], int left, int mid, int right) 
  int i = left;
  int j = mid + 1;
  int k = 0;
  int b[right - left + 1];
  while (i <= mid && j <= right) 
     if (a[i] <= a[j]) 
       b[k++] = a[i++];
      else 
       printf("right element: %d\n", a[j]);
       count += (mid - i + 1);
       printf("new count: %d\n", count);
       b[k++] = a[j++];
     
  
  while (i <= mid)
    b[k++] = a[i++];
  while (j <= right)
    b[k++] = a[j++];
  for (i = left, k = 0; i <= right; i++, k++) 
    a[i] = b[k];
  

【讨论】:

-1 因为所有其他语言的答案都会导致无可救药的太多答案,所有这些基本上都重复了其他答案中已经出现的信息。此外,这本质上是一个没有解释的纯代码答案,充其量只是主要适用于实际有关该语言的问题。【参考方案14】:

这里是c++解决方案

/**
*array sorting needed to verify if first arrays n'th element is greater than sencond arrays
*some element then all elements following n will do the same
*/
#include<stdio.h>
#include<iostream>
using namespace std;
int countInversions(int array[],int size);
int merge(int arr1[],int size1,int arr2[],int size2,int[]);
int main()

    int array[] = 2, 4, 1, 3, 5;
    int size = sizeof(array) / sizeof(array[0]);
    int x = countInversions(array,size);
    printf("number of inversions = %d",x);


int countInversions(int array[],int size)

    if(size > 1 )
    
    int mid = size / 2;
    int count1 = countInversions(array,mid);
    int count2 = countInversions(array+mid,size-mid);
    int temp[size];
    int count3 = merge(array,mid,array+mid,size-mid,temp);
    for(int x =0;x<size ;x++)
    
        array[x] = temp[x];
    
    return count1 + count2 + count3;
    else
        return 0;
    


int merge(int arr1[],int size1,int arr2[],int size2,int temp[])

    int count  = 0;
    int a = 0;
    int b = 0;
    int c = 0;
    while(a < size1 && b < size2)
    
        if(arr1[a] < arr2[b])
        
            temp[c] = arr1[a];
            c++;
            a++;
        else
            temp[c] = arr2[b];
            b++;
            c++;
            count = count + size1 -a;
        
    

    while(a < size1)
    
        temp[c] = arr1[a];
        c++;a++;
    

while(b < size2)
    
        temp[c] = arr2[b];
        c++;b++;
    

    return count;

【讨论】:

【参考方案15】:

另一个 Python 解决方案,简短的。利用内置的 bisect 模块,该模块提供了将元素插入其在已排序数组中的位置以及在已排序数组中查找元素索引的功能。

这个想法是将第n个左边的元素存储在这样的数组中,这样我们就可以很容易地找到大于第n个的元素。

import bisect
def solution(A):
    sorted_left = []
    res = 0
    for i in xrange(1, len(A)):
        bisect.insort_left(sorted_left, A[i-1])
        # i is also the length of sorted_left
        res += (i - bisect.bisect(sorted_left, A[i]))
    return res

【讨论】:

我已经发布了an answer,它对这个问题的所有 Python 答案进行了timeit 比较,因此它包含您的代码。您可能有兴趣查看计时结果。 :D【参考方案16】:

大多数答案都基于MergeSort,但这并不是解决此问题的唯一方法O(nlogn)

我将讨论几种方法。

    使用Balanced Binary Search Tree

    扩充您的树以存储重复元素的频率。 这个想法是当树从根遍历到叶以进行插入时,不断计算更大的节点。

类似的东西。

Node *insert(Node* root, int data, int& count)
    if(!root) return new Node(data);
    if(root->data == data)
        root->freq++;
        count += getSize(root->right);
    
    else if(root->data > data)
        count += getSize(root->right) + root->freq;
        root->left = insert(root->left, data, count);
    
    else root->right = insert(root->right, data, count);
    return balance(root);


int getCount(int *a, int n)
    int c = 0;
    Node *root = NULL;
    for(auto i=0; i<n; i++) root = insert(root, a[i], c);
    return c;

    使用Binary Indexed Tree 创建一个求和 BIT。 从末尾循环并开始查找更大元素的计数。
int getInversions(int[] a) 
    int n = a.length, inversions = 0;
    int[] bit = new int[n+1];
    compress(a);
    BIT b = new BIT();
    for (int i=n-1; i>=0; i--) 
         inversions += b.getSum(bit, a[i] - 1);
         b.update(bit, n, a[i], 1);
     
     return inversions;

    使用Segment Tree 创建一个求和段树。 从数组末尾循环并在[0, a[i]-1] 和更新a[i] with 1 之间进行查询
int getInversions(int *a, int n) 
    int N = n + 1, c = 0;
    compress(a, n);
    int tree[N<<1] = 0;
    for (int i=n-1; i>=0; i--) 
        c+= query(tree, N, 0, a[i] - 1);
        update(tree, N, a[i], 1);
    
    return c;

另外,当使用BITSegment-Tree 时,最好使用Coordinate compression

void compress(int *a, int n) 
    int temp[n];
    for (int i=0; i<n; i++) temp[i] = a[i];
    sort(temp, temp+n);
    for (int i=0; i<n; i++) a[i] = lower_bound(temp, temp+n, a[i]) - temp + 1;


【讨论】:

【参考方案17】:

简单的 O(n^2) 答案是使用嵌套的 for 循环并为每次反转增加一个计数器

int counter = 0;

for(int i = 0; i < n - 1; i++)

    for(int j = i+1; j < n; j++)
    
        if( A[i] > A[j] )
        
            counter++;
        
    


return counter;

现在我想你想要一个更有效的解决方案,我会考虑的。

【讨论】:

对于家庭作业问题,最好提供有用的建议,而不是实际的解决方案。教人钓鱼。 这是每个其他学生都会首先获得的显而易见的解决方案,我想他们的老师想要一个更好的实施方案,让他们获得更多分数。 不一定,要看编程课程的水平。对于初学者来说,这并不是那么简单。 他们很可能想要 n*log(n) 解决方案【参考方案18】:

这是一个计数倒置的 C 代码

#include <stdio.h>
#include <stdlib.h>

int  _mergeSort(int arr[], int temp[], int left, int right);
int merge(int arr[], int temp[], int left, int mid, int right);

/* This function sorts the input array and returns the
   number of inversions in the array */
int mergeSort(int arr[], int array_size)

    int *temp = (int *)malloc(sizeof(int)*array_size);
    return _mergeSort(arr, temp, 0, array_size - 1);


/* An auxiliary recursive function that sorts the input array and
  returns the number of inversions in the array. */
int _mergeSort(int arr[], int temp[], int left, int right)

  int mid, inv_count = 0;
  if (right > left)
  
    /* Divide the array into two parts and call _mergeSortAndCountInv()
       for each of the parts */
    mid = (right + left)/2;

    /* Inversion count will be sum of inversions in left-part, right-part
      and number of inversions in merging */
    inv_count  = _mergeSort(arr, temp, left, mid);
    inv_count += _mergeSort(arr, temp, mid+1, right);

    /*Merge the two parts*/
    inv_count += merge(arr, temp, left, mid+1, right);
  
  return inv_count;


/* This funt merges two sorted arrays and returns inversion count in
   the arrays.*/
int merge(int arr[], int temp[], int left, int mid, int right)

  int i, j, k;
  int inv_count = 0;

  i = left; /* i is index for left subarray*/
  j = mid;  /* i is index for right subarray*/
  k = left; /* i is index for resultant merged subarray*/
  while ((i <= mid - 1) && (j <= right))
  
    if (arr[i] <= arr[j])
    
      temp[k++] = arr[i++];
    
    else
    
      temp[k++] = arr[j++];

     /*this is tricky -- see above explanation/diagram for merge()*/
      inv_count = inv_count + (mid - i);
    
  

  /* Copy the remaining elements of left subarray
   (if there are any) to temp*/
  while (i <= mid - 1)
    temp[k++] = arr[i++];

  /* Copy the remaining elements of right subarray
   (if there are any) to temp*/
  while (j <= right)
    temp[k++] = arr[j++];

  /*Copy back the merged elements to original array*/
  for (i=left; i <= right; i++)
    arr[i] = temp[i];

  return inv_count;


/* Driver progra to test above functions */
int main(int argv, char** args)

  int arr[] = 1, 20, 6, 4, 5;
  printf(" Number of inversions are %d \n", mergeSort(arr, 5));
  getchar();
  return 0;

这里有详细解释:http://www.geeksforgeeks.org/counting-inversions/

【讨论】:

【参考方案19】:

在java中O(n log n)时间,O(n)空间解。

合并排序,通过调整来保留合并步骤中执行的反转次数。 (对于一个很好解释的合并排序,请查看http://www.vogella.com/tutorials/JavaAlgorithmsMergesort/article.html)

由于可以就地进行归并排序,空间复杂度可以提高到O(1)。

当使用这种排序时,反转仅发生在合并步骤中,并且仅当我们必须将第二部分的元素放在前半部分的元素之前,例如

0 5 10 15

合并

1 6 22

我们有 3 + 2 + 0 = 5 次反转:

1 与 5, 10, 15 6 和 10, 15 22 与

在我们进行了 5 次反转之后,我们的新合并列表是 0、1、5、6、10、15、22

在 Codility 上有一个名为 ArrayInversionCount 的演示任务,您可以在其中测试您的解决方案。

    public class FindInversions 

    public static int solution(int[] input) 
        if (input == null)
            return 0;
        int[] helper = new int[input.length];
        return mergeSort(0, input.length - 1, input, helper);
    

    public static int mergeSort(int low, int high, int[] input, int[] helper) 
        int inversionCount = 0;
        if (low < high) 
            int medium = low + (high - low) / 2;
            inversionCount += mergeSort(low, medium, input, helper);
            inversionCount += mergeSort(medium + 1, high, input, helper);
            inversionCount += merge(low, medium, high, input, helper);
        
        return inversionCount;
    

    public static int merge(int low, int medium, int high, int[] input, int[] helper) 
        int inversionCount = 0;

        for (int i = low; i <= high; i++)
            helper[i] = input[i];

        int i = low;
        int j = medium + 1;
        int k = low;

        while (i <= medium && j <= high) 
            if (helper[i] <= helper[j]) 
                input[k] = helper[i];
                i++;
             else 
                input[k] = helper[j];
                // the number of elements in the first half which the j element needs to jump over.
                // there is an inversion between each of those elements and j.
                inversionCount += (medium + 1 - i);
                j++;
            
            k++;
        

        // finish writing back in the input the elements from the first part
        while (i <= medium) 
            input[k] = helper[i];
            i++;
            k++;
        
        return inversionCount;
    


【讨论】:

【参考方案20】:

这里是 O(n*log(n)) perl 实现:

sub sort_and_count 
    my ($arr, $n) = @_;
    return ($arr, 0) unless $n > 1;

    my $mid = $n % 2 == 1 ? ($n-1)/2 : $n/2;
    my @left = @$arr[0..$mid-1];
    my @right = @$arr[$mid..$n-1];

    my ($sleft, $x) = sort_and_count( \@left, $mid );
    my ($sright, $y) = sort_and_count( \@right, $n-$mid);
    my ($merged, $z) = merge_and_countsplitinv( $sleft, $sright, $n );

    return ($merged, $x+$y+$z);


sub merge_and_countsplitinv 
    my ($left, $right, $n) = @_;

    my ($l_c, $r_c) = ($#$left+1, $#$right+1);
    my ($i, $j) = (0, 0);
    my @merged;
    my $inv = 0;

    for my $k (0..$n-1) 
        if ($i<$l_c && $j<$r_c) 
            if ( $left->[$i] < $right->[$j]) 
                push @merged, $left->[$i];
                $i+=1;
             else 
                push @merged, $right->[$j];
                $j+=1;
                $inv += $l_c - $i;
            
         else 
            if ($i>=$l_c) 
                push @merged, @$right[ $j..$#$right ];
             else 
                push @merged, @$left[ $i..$#$left ];
            
            last;
        
    

    return (\@merged, $inv);

【讨论】:

【参考方案21】:

我在 Python 中的回答:

1- 首先对数组进行排序并复制它。在我的程序中,B 代表排序后的数组。 2-遍历原始数组(未排序),并在排序列表中找到该元素的索引。还要记下元素的索引。 3-确保元素没有任何重复项,如果有,则需要将索引的值更改为-1。我的程序中的 while 条件正是这样做的。 4-继续计算将您的索引值的反转,并在计算出它的反转后删除元素。

def binarySearch(alist, item):
    first = 0
    last = len(alist) - 1
    found = False

    while first <= last and not found:
        midpoint = (first + last)//2
        if alist[midpoint] == item:
            return midpoint
        else:
            if item < alist[midpoint]:
                last = midpoint - 1
            else:
                first = midpoint + 1

def solution(A):

    B = list(A)
    B.sort()
    inversion_count = 0
    for i in range(len(A)):
        j = binarySearch(B, A[i])
        while B[j] == B[j - 1]:
            if j < 1:
                break
            j -= 1

        inversion_count += j
        B.pop(j)

    if inversion_count > 1000000000:
        return -1
    else:
        return inversion_count

print solution([4, 10, 11, 1, 3, 9, 10])

【讨论】:

我已经发布了an answer,它对这个问题的所有 Python 答案进行了timeit 比较,因此它包含您的代码。您可能有兴趣查看计时结果。【参考方案22】:

好吧,我有一个不同的解决方案,但恐怕它只适用于不同的数组元素。

//Code
#include <bits/stdc++.h>
using namespace std;

int main()

    int i,n;
    cin >> n;
    int arr[n],inv[n];
    for(i=0;i<n;i++)
        cin >> arr[i];
    
    vector<int> v;
    v.push_back(arr[n-1]);
    inv[n-1]=0;
    for(i=n-2;i>=0;i--)
        auto it = lower_bound(v.begin(),v.end(),arr[i]); 
        //calculating least element in vector v which is greater than arr[i]
        inv[i]=it-v.begin();
        //calculating distance from starting of vector
        v.insert(it,arr[i]);
        //inserting that element into vector v
    
    for(i=0;i<n;i++)
        cout << inv[i] << " ";
    
    cout << endl;
    return 0;

为了解释我的代码,我们继续从 Array 的末尾添加元素。对于任何传入的数组元素,我们找到 向量 v 中第一个元素的索引,它大于我们的传入元素并分配它值到传入元素的索引的反转计数。之后,我们将该元素插入到向量 v 的正确位置,以便向量 v 保持排序顺序。

//INPUT     
4
2 1 4 3

//OUTPUT    
1 0 1 0

//To calculate total inversion count just add up all the elements in output array

【讨论】:

【参考方案23】:

满足 O(N*log(N)) 时间复杂度要求的 C++ 中一种可能的解决方案如下。

#include <algorithm>

vector<int> merge(vector<int>left, vector<int>right, int &counter)


    vector<int> result;

    vector<int>::iterator it_l=left.begin();
    vector<int>::iterator it_r=right.begin();

    int index_left=0;

    while(it_l!=left.end() || it_r!=right.end())
    

        // the following is true if we are finished with the left vector 
        // OR if the value in the right vector is the smaller one.

        if(it_l==left.end() || (it_r!=right.end() && *it_r<*it_l) )
        
            result.push_back(*it_r);
            it_r++;

            // increase inversion counter
            counter+=left.size()-index_left;
        
        else
        
            result.push_back(*it_l);
            it_l++;
            index_left++;

        
    

    return result;


vector<int> merge_sort_and_count(vector<int> A, int &counter)


    int N=A.size();
    if(N==1)return A;

    vector<int> left(A.begin(),A.begin()+N/2);
    vector<int> right(A.begin()+N/2,A.end());

    left=merge_sort_and_count(left,counter);
    right=merge_sort_and_count(right,counter);


    return merge(left, right, counter);


它与常规的合并排序仅在计数器上有所不同。

【讨论】:

这似乎与之前发布的 Java 和 Python 解决方案几乎相同,看似使用相同的算法,因此我认为它并没有增加太多价值。【参考方案24】:

这是我在 Ruby 中的 O(n log n) 解决方案:

def solution(t)
    sorted, inversion_count = sort_inversion_count(t)
    return inversion_count
end

def sort_inversion_count(t)
    midpoint = t.length / 2
    left_half = t[0...midpoint]
    right_half = t[midpoint..t.length]

    if midpoint == 0
        return t, 0
    end

    sorted_left_half, left_half_inversion_count = sort_inversion_count(left_half)
    sorted_right_half, right_half_inversion_count = sort_inversion_count(right_half)

    sorted = []
    inversion_count = 0
    while sorted_left_half.length > 0 or sorted_right_half.length > 0
        if sorted_left_half.empty?
            sorted.push sorted_right_half.shift
        elsif sorted_right_half.empty?
            sorted.push sorted_left_half.shift
        else
            if sorted_left_half[0] > sorted_right_half[0]
                inversion_count += sorted_left_half.length
                sorted.push sorted_right_half.shift
            else
                sorted.push sorted_left_half.shift
            end
        end
    end

    return sorted, inversion_count + left_half_inversion_count + right_half_inversion_count
end

还有一些测试用例:

require "minitest/autorun"

class TestCodility < Minitest::Test
    def test_given_example
        a = [-1, 6, 3, 4, 7, 4]
        assert_equal solution(a), 4
    end

    def test_empty
        a = []
        assert_equal solution(a), 0
    end

    def test_singleton
        a = [0]
        assert_equal solution(a), 0
    end

    def test_none
        a = [1,2,3,4,5,6,7]
        assert_equal solution(a), 0
    end

    def test_all
        a = [5,4,3,2,1]
        assert_equal solution(a), 10
    end

    def test_clones
        a = [4,4,4,4,4,4]
        assert_equal solution(a), 0
    end
end

【讨论】:

【参考方案25】:

最好的优化方法是通过合并排序来解决它,我们可以通过比较左右数组来检查需要多少反转。只要左边数组的元素大于右边数组的元素,就会反转。

归并排序方法:-

这里是代码。代码与合并排序完全相同,除了 mergeToParent 方法下的代码 sn-p 我在 (left[leftunPicked] &lt; right[rightunPicked]) 的 else 条件下计算反转

public class TestInversionThruMergeSort 
    
    static int count =0;

    public static void main(String[] args) 
        int[] arr = 6, 9, 1, 14, 8, 12, 3, 2;
        

        partition(arr);

        for (int i = 0; i < arr.length; i++) 

            System.out.println(arr[i]);
        
        
        System.out.println("inversions are "+count);

    

    public static void partition(int[] arr) 

        if (arr.length > 1) 

            int mid = (arr.length) / 2;
            int[] left = null;

            if (mid > 0) 
                left = new int[mid];

                for (int i = 0; i < mid; i++) 
                    left[i] = arr[i];
                
            

            int[] right = new int[arr.length - left.length];

            if ((arr.length - left.length) > 0) 
                int j = 0;
                for (int i = mid; i < arr.length; i++) 
                    right[j] = arr[i];
                    ++j;
                
            

            partition(left);
            partition(right);
            mergeToParent(left, right, arr);
        

    

    public static void mergeToParent(int[] left, int[] right, int[] parent) 

        int leftunPicked = 0;
        int rightunPicked = 0;
        int parentIndex = -1;

        while (rightunPicked < right.length && leftunPicked < left.length) 

            if (left[leftunPicked] < right[rightunPicked]) 
                parent[++parentIndex] = left[leftunPicked];
                ++leftunPicked;

             else 
                count = count + left.length-leftunPicked;
                if ((rightunPicked < right.length)) 
                    parent[++parentIndex] = right[rightunPicked];
                    ++rightunPicked;
                
            

        

        while (leftunPicked < left.length) 
            parent[++parentIndex] = left[leftunPicked];
            ++leftunPicked;
        

        while (rightunPicked < right.length) 
            parent[++parentIndex] = right[rightunPicked];
            ++rightunPicked;
        

    


我们可以将输入数组与排序数组进行比较的另一种方法:- 暗黑破坏神回答的这个实现。虽然这不应该是首选方法,因为从数组或列表中删除 n 个元素是 log(n^2)。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;


public class TestInversion 

    public static void main(String[] args) 
        
        Integer [] arr1 = 6, 9, 1, 14, 8, 12, 3, 2;
        
        List<Integer> arr = new ArrayList(Arrays.asList(arr1));
        List<Integer> sortArr = new ArrayList<Integer>();
        
        for(int i=0;i<arr.size();i++)
            sortArr.add(arr.get(i));
         
        
        
            
        Collections.sort(sortArr);
        
        int inversion = 0;
        
        Iterator<Integer> iter = arr.iterator();
        
        while(iter.hasNext())
            
            Integer el = (Integer)iter.next();
            int index = sortArr.indexOf(el);
            
            if(index+1 > 1)
                inversion = inversion + ((index+1)-1);
            
            
            //iter.remove();
            sortArr.remove(el);
            
        
        
        System.out.println("Inversions are "+inversion);
        
        
        

    



【讨论】:

【参考方案26】:

大小为n 的列表可能的最大反转次数可以由表达式概括:

maxPossibleInversions = (n * (n-1) ) / 2

所以对于大小为6 的数组,最大可能的反转将等于15

为了达到n logn 的复杂度,我们可以在归并排序中背负反演算法。

以下是通用步骤:

将数组一分为二 调用mergeSort 例程。如果左子数组中的元素大于右子数组中的元素使inversionCount += leftSubArray.length

就是这样!

这是一个简单的例子,我用 javascript 做的:

var arr = [6,5,4,3,2,1]; // Sample input array

var inversionCount = 0;

function mergeSort(arr) 
    if(arr.length == 1)
        return arr;

    if(arr.length > 1) 
        let breakpoint = Math.ceil((arr.length/2));
        // Left list starts with 0, breakpoint-1
        let leftList = arr.slice(0,breakpoint);
        // Right list starts with breakpoint, length-1
        let rightList = arr.slice(breakpoint,arr.length);

        // Make a recursive call
        leftList = mergeSort(leftList);
        rightList = mergeSort(rightList);

        var a = merge(leftList,rightList);
        return a;
    


function merge(leftList,rightList) 
    let result = [];
    while(leftList.length && rightList.length) 
        /**
         * The shift() method removes the first element from an array
         * and returns that element. This method changes the length
         * of the array.
         */
        if(leftList[0] <= rightList[0]) 
            result.push(leftList.shift());
        else
            inversionCount += leftList.length;
            result.push(rightList.shift());
        
    

    while(leftList.length)
        result.push(leftList.shift());

    while(rightList.length)
        result.push(rightList.shift());

    console.log(result);
    return result;


mergeSort(arr);
console.log('Number of inversions: ' + inversionCount);

【讨论】:

【参考方案27】:

在 Swift 中使用归并排序实现数组中的倒数计数:

注意交换次数是递增的

nSwaps += mid + 1 - iL 

(即数组左侧的相对长度减去左侧当前元素的索引)

...因为这是数组右侧的元素必须跳过的元素数(反转次数)才能排序。

func merge(arr: inout [Int], arr2: inout [Int], low: Int, mid: Int, high: Int) -> Int 
    var nSwaps = 0;

    var i = low;
    var iL = low;
    var iR = mid + 1;

    while iL <= mid && iR <= high 
        if arr2[iL] <= arr2[iR] 
            arr[i] = arr2[iL]
            iL += 1
            i += 1
         else 
            arr[i] = arr2[iR]
            nSwaps += mid + 1 - iL
            iR += 1
            i += 1
        
    

    while iL <= mid 
        arr[i] = arr2[iL]
        iL += 1
        i += 1
    

    while iR <= high 
        arr[i] = arr2[iR]
        iR += 1
        i += 1
    

    return nSwaps


func mergeSort(arr: inout [Int]) -> Int 
    var arr2 = arr
    let nSwaps = mergeSort(arr: &arr, arr2: &arr2, low: 0, high: arr.count-1)
    return nSwaps


func mergeSort(arr: inout [Int], arr2: inout [Int], low: Int, high: Int) -> Int 

    if low >= high 
        return 0
    

    let mid = low + ((high - low) / 2)

    var nSwaps = 0;
    nSwaps += mergeSort(arr: &arr2, arr2: &arr, low: low, high: mid)
    nSwaps += mergeSort(arr: &arr2, arr2: &arr, low: mid+1, high: high)
    nSwaps += merge(arr: &arr, arr2: &arr2, low: low, mid: mid, high: high)

    return nSwaps


var arrayToSort: [Int] = [2, 1, 3, 1, 2]
let nSwaps = mergeSort(arr: &arrayToSort)

print(arrayToSort) // [1, 1, 2, 2, 3]
print(nSwaps) // 4

【讨论】:

【参考方案28】:

我最近不得不在 R 中这样做:

inversionNumber <- function(x)
    mergeSort <- function(x)
        if(length(x) == 1)
            inv <- 0
         else 
            n <- length(x)
            n1 <- ceiling(n/2)
            n2 <- n-n1
            y1 <- mergeSort(x[1:n1])
            y2 <- mergeSort(x[n1+1:n2])
            inv <- y1$inversions + y2$inversions
            x1 <- y1$sortedVector
            x2 <- y2$sortedVector
            i1 <- 1
            i2 <- 1
            while(i1+i2 <= n1+n2+1)
                if(i2 > n2 || i1 <= n1 && x1[i1] <= x2[i2])
                    x[i1+i2-1] <- x1[i1]
                    i1 <- i1 + 1
                 else 
                    inv <- inv + n1 + 1 - i1
                    x[i1+i2-1] <- x2[i2]
                    i2 <- i2 + 1
                
            
        
        return (list(inversions=inv,sortedVector=x))
    
    r <- mergeSort(x)
    return (r$inversions)

【讨论】:

@Dukeling 是什么促使您撤回了您的评论而不是您的反对票?【参考方案29】:

Java 实现:

import java.lang.reflect.Array;
import java.util.Arrays;


public class main 

public static void main(String[] args) 
    int[] arr = 6, 9, 1, 14, 8, 12, 3, 2;
    System.out.println(findinversion(arr,0,arr.length-1));


public static int findinversion(int[] arr,int beg,int end) 
    if(beg >= end)
        return 0;

    int[] result = new int[end-beg+1];
    int index = 0;
    int mid = (beg+end)/2;
    int count = 0, leftinv,rightinv;
    //System.out.println("...."+beg+"   "+end+"  "+mid);
    leftinv = findinversion(arr, beg, mid);
    rightinv = findinversion(arr, mid+1, end);
    l1:
    for(int i = beg, j = mid+1; i<=mid || j<=end;/*index < result.length;*/ ) 
        if(i>mid) 
            for(;j<=end;j++)
                result[index++]=arr[j];
            break l1;
        
        if(j>end) 
            for(;i<=mid;i++)
                result[index++]=arr[i];
            break l1;
        
        if(arr[i] <= arr[j]) 
            result[index++]=arr[i];
            i++;    
         else 
            System.out.println(arr[i]+"  "+arr[j]);
            count = count+ mid-i+1;
            result[index++]=arr[j];
            j++;    
        
    

    for(int i = 0, j=beg; i< end-beg+1; i++,j++)
        arr[j]= result[i];
    return (count+leftinv+rightinv);
    //System.out.println(Arrays.toString(arr));



【讨论】:

-1 因为所有其他语言的答案都会导致无可救药的太多答案,所有这些基本上都重复了其他答案中已经出现的信息。此外,这本质上是一个没有解释的纯代码答案,充其量只是主要适用于实际有关该语言的问题。 @Dukeling 非常欢迎 Anwit 加入社区。他的第一个答案被否决了,因为他尝试过。你真好。 @VenkatSudheerReddyAedama 公平地说,他已经发布了 6 个答案,而无用的答案是没有用的,无论发布者有多少声誉。我们的投票应该针对内容,而不是用户。 @Dukeling 内容不是由以太制成的。它来自用户。这个答案可能对您没有帮助,但它绝对可以帮助正在寻找 Java 答案的人。由于同样的原因,您对我的答案 (***.com/questions/337664/…) 投了反对票,但我敢打赌,如果有人在 Scala 中寻找相同的解决方案,它会有所帮助。如果您关心算法/解释,那么有些用户关心特定语言的实现,这就是您看到不同语言答案的原因。 @VenkatSudheerReddyAedama 语言太多,无法在此处为每种语言保留答案,尤其是考虑到这里已经介绍了六种以上的方法(它可能已经如果绝对只有一种方法可以做到这一点,那就另当别论了)。太多的答案过多地稀释了答案 - 坦率地说,阅读几乎一打相同的方法是浪费时间,特别是当答案中唯一的非代码是“Java 实现”时(所以我必须阅读代码才能弄清楚是关于)。 (发布时这里已经有两个 Java 答案)【参考方案30】:

这是我对 Scala 的看法:

trait MergeSort 
  def mergeSort(ls: List[Int]): List[Int] = 
    def merge(ls1: List[Int], ls2: List[Int]): List[Int] =
      (ls1, ls2) match 
        case (_, Nil) => ls1
        case (Nil, _) => ls2
        case (lowsHead :: lowsTail, highsHead :: highsTail) =>
          if (lowsHead <= highsHead) lowsHead :: merge(lowsTail, ls2)
          else highsHead :: merge(ls1, highsTail)
      

    ls match 
      case Nil => Nil
      case head :: Nil => ls
      case _ =>
        val (lows, highs) = ls.splitAt(ls.size / 2)
        merge(mergeSort(lows), mergeSort(highs))
    
  


object InversionCounterApp extends App with MergeSort 
  @annotation.tailrec
  def calculate(list: List[Int], sortedListZippedWithIndex: List[(Int, Int)], counter: Int = 0): Int =
    list match 
      case Nil => counter
      case head :: tail => calculate(tail, sortedListZippedWithIndex.filterNot(_._1 == 1), counter + sortedListZippedWithIndex.find(_._1 == head).map(_._2).getOrElse(0))
    

  val list: List[Int] = List(6, 9, 1, 14, 8, 12, 3, 2)
  val sortedListZippedWithIndex: List[(Int, Int)] = mergeSort(list).zipWithIndex
  println("inversion counter = " + calculate(list, sortedListZippedWithIndex))
  // prints: inversion counter = 28 

【讨论】:

-1 因为所有其他语言的答案都会导致无可救药的太多答案,所有这些基本上都重复了其他答案中已经出现的信息。此外,这本质上是一个没有解释的纯代码答案,充其量只是主要适用于与该语言有关的问题。 ... 哦,我发现 Scala 的语法有些难以阅读(可能是因为我没有使用它或类似语言的经验,但这是重点的一部分 - 这不是一个 Scala 问题,所以我不应该有)。尾递归(如果这是与其他答案的主要/唯一区别),在大多数情况下,是一种优化,而不是对算法的根本改变,即不足以证明单独的答案 - 你也没有提到任何东西关于答案中的尾递归。 发现代码样本之间的共同模式并不需要太多 - 它不是万无一失的,但它是相似性的一个很好的指示 - 鉴于这不是我对答案的唯一问题,它不是如果我弄错了,那真是一场火车撞车。但这并不意味着我实际上可以很好地阅读代码以理解它。 Stack Overflow 是一个问答网站,而不是代码库。

以上是关于计算数组中的反转的主要内容,如果未能解决你的问题,请参考以下文章

计算 nlogn 中的反转

c_cpp 在数组中计算反转

数组反转

js 数组反转, 不改变原来的数组

Java数组列表反转

如何反转多维数组中的重复值