1. Overview
In this tutorial, we’ll discuss the problem of counting inversions in an array. Firstly, we’ll define the problem and provide an example that explains the meaning of an inversion.
Secondly, we’ll discuss two approaches to solving the problem and talk about the good usage of the problem.
2. Defining the Problem
Suppose we have an array of
integers, which are enumerated from
to
. The value of the
element is equal to
.
An inversion is a pair of indices that satisfies the following:
In other words, an inversion is a pair of two distinct indices such that the order of these two indices is the reverse order of their values.
The problem gives us an array and asks to compute the number of inversions in this array.
Let’s check the example for better understanding:
Suppose we have an array . The answer in this case is
because we have
,
and
as inversions.
Note that when the array is sorted increasingly, the number of inversions is equal to . On the other hand, when the array is sorted decreasingly, the number of inversions is the maximum possible over all the permutations of the array (it will be equal to
when elements are pairwise distinct).
In other words, the number of inversions refers to how far the array is from being sorted.
3. Naive Approach
3.1. Main Idea
The idea is straightforward. We p****erform a brute force by iterating over all pairs of indices and check whether this pair satisfies the condition mentioned in section 2.
3.2. Implementation
Let’s take a look at the implementation:
algorithm NaiveCountInversions(A, n):
// INPUT
// A = The array to process
// n = The size of array A
// OUTPUT
// Returns the number of inversions in the array A
answer <- 0
for i <- 0 to n - 2:
for j <- i + 1 to n - 1:
if A[i] > A[j]:
answer <- answer + 1
return answer
Firstly, we initialize with zero, which will hold the resulting answer. Then, we start by iterating over the first element
of the pair. Then, we iterate over the second element
. Note that since we start
from
, we guarantee that
.
Next, we check the pair to see whether it satisfies the inversions condition or not. If yes then we have a new inversion, and we increase the total number of inversions by one.
Finally, we return the resulting .
The complexity of the naive approach is , where
is the length of
.
4. Divide-And-Conquer Approach
4.1. Theoretical Idea
Suppose we want to count the number of inversions inside the array . Let’s divide it into two equal Arrays and call the first one
and the second one
. Now an inversion
can have one of the following types:
-
and
.
-
and
.
-
and
.
Consider the first type. The problem turns out to computing for each index , the number of indices
such that
.
Now suppose that both arrays and
are sorted increasingly, then we can use the two-pointers technique to solve it. That’s because indices
that satisfy the condition for an index
always form a prefix from the array
, when
gets bigger, the length of the prefix increases.
But, what about the second and third types? The answer is simple: just perform the same algorithm recursively for both and
independently.
Back to the first type, we supposed that both and
are sorted increasingly. To achieve that, the algorithm must sort them. In other words, after applying the algorithm to both
and
, they become sorted. To sort
we just merge the two sorted arrays
and
into
.
4.2. Implementation
Let’s start by implementing a function that counts the number of inversions of the first type:
algorithm CountInvOfFirstType(L, R, lenL, lenR):
// INPUT
// L = The left subarray
// R = The right subarray
// lenL = The length of the first subarray L
// lenR = The length of the second subarray R
// OUTPUT
// Returns the number of inversions where the first element is in L and the second in R
answer <- 0
j <- 0
for i <- 0 to lenL - 1:
while j < lenR and R[j] < L[i]:
j <- j + 1
answer <- answer + j
return answer
Firstly, we initialize with zero, which will hold the resulting answer. Then, we initialize
with zero, which is the first index in the
that breaks the condition for the current
. More formally, it’s the first index such that
. Since we’re using zero-indexed arrays,
represents the length of the prefix from
that satisfies the condition.
While we increase , we must increase
as well. This continues until it breaks the condition again.
After computing the correct , we add it to the
as the number of elements in
less than
.
Finally, we return the resulting .
Now, let’s take a look at the implementation of the complete algorithm:
algorithm DivideAndConquerCountInversions(A, n):
// INPUT
// A = The array to process
// n = The size of array A
// OUTPUT
// Returns the total number of inversions in the array A
if n <= 1:
return 0
// Divide the array into two halves
lenL <- n / 2
lenR <- n - lenL
L <- A[0: lenL-1]
R <- A[lenL: n]
// Recursively count inversions in each half
answer <- countInv(L, lenL) + DivideAndConquerCountInversions(R, lenR)
// Count inversions of the first type and merge both halves
answer <- answer + CountInvOfFirstType(L, R, lenL, lenR)
merge(A, L, R, lenL, lenR)
return answer
Firstly, we check whether the length of the array is less than or equal to . If so, then the number of inversions is equal to
.
Otherwise, we get and
as the first and second halves of the array using the function
that gets the subarray from
(inclusive) to
(exclusive).
Next, We add the number of inversions in and
by calling
recursively on both halves. Since both
and
become sorted, we can compute the number of inversions of the first type using the
function.
After that, we merge the two sorted arrays into the array using
function so that it becomes sorted as well. We assume that both functions
and
take a reference to the array
rather than taking a copy. Thus, when it gets edited inside the function, the edit will affect the original array.
4.3. Complexity
We note that the previous algorithm is very similar to the merge sort algorithm. The only difference is that we make another iteration on the elements of the array when we count the number of inversions of the first type (the first iteration was when we merged the two subarrays).
Therefore, the complexity is the same as the merge sort algorithm, which is .
5. Usage
Suppose we have an array , and we want to find the minimum number of operations to get the array sorted. In one operation, we can swap any two adjacent elements.
The answer is the number of inversions. That’s because, in each operation, we decrease the number of inversions by . When the array is sorted, the number of inversions will be equal to
.
6. Conclusion
In this tutorial, we presented the problem of counting the number of inversions in an array. We explained the general idea and discussed two approaches for solving it. Also, we provided a fair usage of the discussed problem.