Solution: Number of Inversions

Solve the Number of Inversions Problem.

We'll cover the following

Solution

Let’s try the most frequently used type of the divide-and-conquer strategy. Split the input sequence into two halves, LeftHalfLeftHalf and RightHalfRightHalf, and make a recursive call for each of them. This allows us to compute all inversions that lie in the same half. But it does not reveal the numbers of split inversions, i.e., the number of pairs (ai,aj)(a_i, a_j) such that aia_i lies in the left half, aja_j lies in the right half, and ai>aja_i >a_j.

Stop and think: Consider an element xx in LeftHalfLeftHalf. What is the number of split inversions that xx belongs to?

Given an array ListList and an integer xx, let ListxList_x be the number of elements in ListList that are smaller than xx. Since the answer to the question above is RightHalfxRightHalf_x, our goal is to rapidly compute ListxList_x.

It is equal to the number of elements in RightHalfRightHalf that are smaller than xx. This way, we face the following problem: given a sequence of integers ListList and an integer xx, find the number of elements in ListList that are smaller than xx. We can do it in O(List)O(|List|) time in the case of the unordered array (since each element of the array has to be checked) and in O(logList)O(log |List|) time in the case of an ordered array by using the binary search.

Exercise break: Show how to implement a method CountSmaller(List,x)CountSmaller(List, x) for counting the number of elements of ListList that are smaller than xx in time O(log2List)O(log_2 |List|).

This way, we arrive at the following divide-and-conquer algorithm:


 CountInversions(List)CountInversions(List):
 ifList1if |List| ≤ 1:
 return 00
 inversions0inversions ← 0
 LeftHalflefthalfLeftHalf ← left half of ListList
 RightHalfrighthalfRightHalf ← right half of ListList
 inversionsinversions+inversions ← inversions + CountInversionsCountInversions(LeftHalf)(LeftHalf)
 inversionsinversions+inversions ← inversions + CountInversionsCountInversions(RightHalf)(RightHalf)
 sort RightHalfRightHalf
 for xx in LeftHalfLeftHalf:
inversionsinversionsinversions ← inversions + CountSmallerCountSmaller(RightHalf,x)(RightHalf, x)$
 return inversionsinversions


The running time T(n)T (n) (where nn is the length of ListList) satisfies a recurrence relation

T(n)2T(n/2)+O(nlogn).T(n)≤2T(n/2)+O(nlogn).

The O(nlogn)O(nlogn) term includes two things: sorting RightHalfRightHalf and answering n/2n/2 CountSmallerCountSmaller queries. This recurrence cannot be plugged into the master theorem directly as the term O(nlogn)O(nlogn) is not of the form O(nd)O(n^d) for a constant dd. Still, we can analyze this recurrence in the same fashion. The recursion tree has log2nlog_2 n levels, the total size of all problems on every level is equal to nn, and the total time spent on every level is O(nlogn)O(nlogn). Therefore, the total running time is O(nlog2n)O(n log^2 n). Instead of formally proving it, we will improve the above algorithm so that it works in time O(nlogn)O(nlogn).

Level up your interview prep. Join Educative to access 70+ hands-on prep courses.