一、题目
给定一个数组 nums ,如果 i < j 且 nums[i] > 2*nums[j] 我们就将 (i, j) 称作一个重要翻转对。
你需要返回给定数组中的重要翻转对的数量。
示例 1:
输入: [1,3,2,3,1]
输出: 2
示例 2:
输入: [2,4,3,5,1]
输出: 3
注意:
给定数组的长度不会超过50000。
输入数组中的所有数字都在32位整数的表示范围内。
二、解决
1、暴力
思路: 略。
代码: 略。
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
空间复杂度:
O
(
1
)
O(1)
O(1)
2、归并排序
先附一下归并排序模板:
public static void mergeSort(int[] array, int left, int right) {
if (right <= left) return;
int mid = (left + right) >> 1; // (left+right)/2
mergeSort(array, left, mid);
mergeSort(array, mid+1, right);
merge(array, left, mid, right);
}
public static void merge(int[] arr, int left, int mid, int right) {
int[] temp = new int[right-left+1]; // 中间数组
int i = left, j = mid+1, k = 0;
while (i<=mid && j<=right) {
temp[k++] = arr[i]<=arr[j] ? arr[i++] : arr[j++];
}
while (i<=mid) temp[k++] = arr[i++];
while (j<=right) temp[k++] = arr[j++];
for (int p=0; p<temp.length; p++) {
arr[left+p] = temp[p];
}
// 也可以用 System.arraycopy(sourceArr, sourceStartIndex, destArr, destStartIndex, copyLength);
}
思路-版本1:
归并排序隐含了逆序对的数量计算,每一次合并(Merge)的过程,左半边和右半边都是分别有序的。
当我们用双指针合并左右半边的时候,当nums[left] > 2*nums[right]
,代表当前的nums[right]
要小于nums[left] ~ nums[m]
的所有数(本应大于),也就是有m - left + 1
个逆序对。
代码-版本1(推荐):
public class Solution {
public int reversePairs(int[] nums) {
if (nums == null || nums.length == 0) return 0;
return mergeSort(nums, 0, nums.length - 1);
}
private int mergeSort(int[] nums, int l, int r) {
if (l >= r) return 0;
int mid = l + (r - l)/2;
int count = mergeSort(nums, l, mid) + mergeSort(nums, mid + 1, r);
int[] cache = new int[r - l + 1];
int i = l, t = l, c = 0;
for (int j = mid + 1; j <= r; j++, c++) {
while (i <= mid && nums[i] <= 2 * (long)nums[j]) i++;
while (t <= mid && nums[t] < nums[j]) cache[c++] = nums[t++];
cache[c] = nums[j];
count += mid - i + 1;
}
while (t <= mid) cache[c++] = nums[t++];
System.arraycopy(cache, 0, nums, l, r - l + 1);
return count;
}
}
思路-版本2:
思路类似上面,不过处理过程略有不同。
代码-版本2:
public class Solution {
public int reversePairs(int[] nums) {
return mergeSort(nums, 0, nums.length-1);
}
private int mergeSort(int[] nums, int s, int e){
if(s>=e) return 0;
int mid = s + (e-s)/2;
int cnt = mergeSort(nums, s, mid) + mergeSort(nums, mid+1, e);
for(int i = s, j = mid+1; i<=mid; i++){
while(j<=e && nums[i]/2.0 > nums[j]) j++;
cnt += j-(mid+1);
}
Arrays.sort(nums, s, e+1);
return cnt;
}
}
思路-版本3:
理解关键在于左 && 右数组是有序的。具体请结合代码注释来理解。
代码-版本3:
public class Solution {
public int ret = 0;
public int reversePairs(int[] nums) {
mergeSort(nums, 0, nums.length-1);
return ret;
}
public void mergeSort(int[] nums, int left, int right) {
if (right <= left) {
return;
}
int middle = left + (right - left)/2;
mergeSort(nums, left, middle);
mergeSort(nums,middle+1, right);
merge(nums, left, middle, right);
}
public void merge(int[] nums, int left, int middle, int right) {
// count elements
int count = 0;
for (int l = left, r = middle+1; l <= middle;) {
if (r > right || (long)nums[l] <= 2*(long)nums[r]) { // 右侧数组移到最后一个位置 或者 正常状态
l++; // 左移一位,看下一个数是否符合翻转对条件
ret += count; // nums[x]>2*nums[r],翻转对数量为count,而l>x, nums[l]>nums[x],则翻转对数量也为count
} else { // 满足翻转对条件
r++; // 继续右移一位
count++; // nums[l]>2*nums[r-1],翻转对数量为count,nums[l]>2*nums[r],翻转对数量为count+1
}
}
// merge sort
int[] temp = new int[right - left + 1];
for (int l = left, r = middle+1, k = 0; l <= middle || r <= right;) {
if (l <= middle && ((r > right) || nums[l] < nums[r])) {
temp[k++] = nums[l++];
} else {
temp[k++] = nums[r++];
}
}
for (int p = 0; p < temp.length; p++) {
nums[left + p] = temp[p];
}
}
}
时间复杂度:
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
空间复杂度:
O
(
n
)
O(n)
O(n)
3、树状数组
思路: 暂略。
代码:
private int search(int[] bit, int i) {
int sum = 0;
while (i < bit.length) {
sum += bit[i];
i += i & -i;
}
return sum;
}
private void insert(int[] bit, int i) {
while (i > 0) {
bit[i] += 1;
i -= i & -i;
}
}
public int reversePairs(int[] nums) {
int res = 0;
int[] copy = Arrays.copyOf(nums, nums.length);
int[] bit = new int[copy.length + 1];
Arrays.sort(copy);
for (int ele : nums) {
res += search(bit, index(copy, 2L * ele + 1));
insert(bit, index(copy, ele));
}
return res;
}
private int index(int[] arr, long val) {
int l = 0, r = arr.length - 1, m = 0;
while (l <= r) {
m = l + ((r - l) >> 1);
if (arr[m] >= val) {
r = m - 1;
} else {
l = m + 1;
}
}
return l + 1;
}
时间复杂度:
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
空间复杂度:
O
(
n
)
O(n)
O(n)
三、参考
1、剑指 Offer 51. 数组中的逆序对[Hard]
2、Very Short and Clear MergeSort & BST Java Solutions
3、Java merge sort solution, O(nlog(n))
4、<= 20 lines Java code. Beats 100%!!
5、翻转对
6、General principles behind problems similar to “Reverse Pairs”
7、Very Short and Clear MergeSort & BST Java Solutions