Sorting algorithms can come in very handy when we are comparing numbers in an array. In today's problem, we will be revisiting merge sort, a sorting algorithm with time complexity of O(nlogn).
Let's have a quick recap on how merge sort works:
1) Divide the entire array into two equal(or almost equal) parts.
2) Sort the left partition
3) Sort the right partition
4) Combine the two partition by comparing their first element that was not put back into the original array repeatedly
As for how we should sort the two partitions, MERGE SORT! Recursion is truly powerful:
vector<int> temp;
void merge_sort(vector<int>& nums, int left, int right){
if(left == right) return;
int mid = (left + right) / 2; // find out the middle
merge_sort(nums, left, mid); // sort left partition
merge_sort(nums, mid + 1, right); // sort right partition
for(int i = left; i <= right; i++){
temp.push_back(nums[I]); // create a copy
}
int i1 = left, i2 = mid + 1;
for(int curr = left; curr <= right; curr++){
// combining the two partitions in ascending order
if(i1 == mid + 1)
nums[curr] = temp[i2++];
else if(i2 > right)
nums[curr] = temp[i1++];
else if (temp[i1] <= temp[i2])
nums[curr] = temp[i1++];
else
nums[curr] = temp[i2++];
}
return;
}
Merge sort's quick sorting time can save us from some unwanted runtime error when encoutering LeetCode problem:
Tackling this problem in a brute-force manner is doable, yet LeetCode does not like it. I have tried solving it with bubble sort, another sorting algorithm but with a time complexity of in worst scenario, and resulted in a catastrophic failure. Such reckless approach would not be acceptable, and was also incorrect on multiple attempts.
But here comes our saviour - merge sort, saving us from the formidable runtime error:
class Solution {
vector<int> temp;
int count = 0; // stores the number of reverse pairs
public:
void merge_sort(vector<int>& nums, int left, int right){
// good old merge sort
if(left == right) return;
int mid = (left + right) / 2;
merge_sort(nums, left, mid);
merge_sort(nums, mid + 1, right);
for(int i = left; i <= right; i++){
temp[i] = nums[i];
}
// ** counting logic **
int g = mid + 1;
for(int i = left; i <= mid; i++){
for(; g <= right && long(temp[i]) > 2*long(temp[g]); g++);
count += g - mid - 1;
}
// back to good old merge sort
int i1 = left, i2 = mid + 1;
for(int curr = left; curr <= right; curr++){
if(i1 == mid + 1)
nums[curr] = temp[i2++];
else if(i2 > right)
nums[curr] = temp[i1++];
else if (temp[i1] <= temp[i2])
nums[curr] = temp[i1++];
else
nums[curr] = temp[i2++];
}
return;
}
int reversePairs(vector<int>& nums) {
if(nums.size() == 1)
return 0;
for(int i = 0; i < nums.size(); i++)
temp.push_back(nums[i]); // create a copy
merge_sort(nums, 0, nums.size() - 1);
return count;
}
};
My solution is almost identical to that of the merge sort codes above. But one thing to be extremely careful of is where the counting takes place. Counting has to be done before merging the two partitions together. If it were done otherwise, the result may not be entirely correct as the places between elements were changed and by then you cannot figure out which actually comes first in the array.
But then you may ask, why is it possible for you to merge and count on the go? That is because we count and order the elements from the lowest level, i.e. splitting the elements until they were either alone or paired with another one. We then carry out the comparison and sorting and move on to a level upwards. From the perspective on the higher level, i.e. the groupings of 1-2 elements, the high value elements (those that we care very much about as it may satisfy the requiremenet) still remain in the same position / block. When we carry out comparison, their index would still come before those that were less than half of their value. The same applies up to the surface level.
Another point worth nothing is how counting is implemented. We first check for the first element in the left partition and look for the range of elements in the right partition that satisfy the condition. We will then add the range to the total count and repeate the entire process with the next element. Note that the int g in for-loop was never reset since both partition were sorted, the range can only increase.
Again, special thanks to justin :)
P.S. Please pay close attention to the ';' after the for-loop in the counting logic. It had been missing for quite a while and I had no idea where my code is defected. It costs me at least an hour to find out... TT