Problem
Given an integer array, count the number of inversions.
What is an inversion?
This was taught in Algorithms: Design and Analysis Part I on coursera - how to count inversions.
Inversion count is the distance of order of elements in an array from the the sorted order i.e. it indicates how far (or close) the array is from being sorted. If array is already sorted then inversion count is 0. If array is sorted in reverse order that inversion count is the maximum.
In other words, two elements a[i]
and a[j]
form an inversion if a[i] > a[j] and i < j
Examples
Example 1:
|
|
Example 2:
|
|
Solution
Method 1 - Brute Force
The brute force solution for this problem would be to traverse the array from right to left and count number of smaller element on the right in O(n^2).
Code
|
|
Complexity
- ⏰ Time complexity:
O(n^2)
- 🧺 Space complexity:
O(1)
Method 2 - Divide and Conquer
Can we do it a better way? Yes.
We’ll use the divide and conquer method and piggyback on the MergeSort algorithm to do it in O(nlogn). By counting the number of inversions, we will also be sorting it.
An observation is that if we split the array into two halves such that both halves are sorted in increasing order then inversion count of each partition is 0. However, there might be cross inversion across the partitions. For example , Left=[2, 5, 6]
and Right=[1, 3, 8]
. Then we can see that there are 5 cross inversions (2,1), (5,1), and (5,3), (6,1), (6,3). But computing such cross inversion in pairwise manner would take O(n^2) and certainly not worthy.
By splitting the array in half, we know that:
|
|
But, the number of split inversions will actually be counted when we do the merge subroutine. So the way merge works is that it will keep track of two pointers, one to the front of each half array. It will take the smaller element and increment the pointer. If an element is taken from the second array, we know that it must be smaller than the element in the first subarray, and subsequently every element thereafter. Then we can just sum up the number of elements left in the first array each time the second subarray is chosen from before the first.
Code
|
|
Complexity
- Time Complexity: O(n log n), The algorithm used is divide and conquer, So in each level one full array traversal is needed and there are log n levels so the time complexity is O(n log n).
- Space Compelxity: O(1), No extra space is required.
Here is alternative code:
|
|
Method 3 - Using Self Balanced Binary Tree, O(NlgN)
But we can do it more efficiently using balanced BST such as AVL tree or Red black tree. Observe that, If a key is greater than root, then it is greater than all the nodes in left subtree of root.
So, We will consider each number in an array as a BST node and insert them in a AVL tree one by one from right to left. During each insert we will keep updating the size of left subtree at the node being inserted. This will give us our desired smaller element count. We also need to handle balancing the tree while insert.
A balance tree got imbalanced on an insert when the insert on a subtree rooted at a node x makes difference between left subtree and right subtree height more than 1. We will have to consider 4 cases:
- Insert-in-left-of-left-subtree of x: we can balance by rotating x right
- Insert-in-right-of-right-subtree of x: we can balance by rotating x left
- Insert-in-right-of-left-subtree of x: we need two rotations: balance left of x by rotating left. And then a right rotation of x.
- Insert-in-left-of-right-subtree of x: we need two rotations: balance right of x by rotating right. And then a left rotation of x.
Time complexity will be O(nlgn) and space is O(n).
Method 4 - Using Insertion Sort, O(n+k)
If we know number of inversions(k) are quite low, i.e. n >> k
, we can use insertion sort. Merge sort’s best-case time is still O(n log n)
, whereas insertion sort’s best case is O(n)
.
Code
|
|
Dry Run
|
|