Nameless Site

But one day, you will stand before its decrepit gate,without really knowing why.

0%

数组中的逆序对

来自Leetcode面试题51.数组中的逆序对

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

示例 1:

1
2
输入: [7,5,6,4]
输出: 5

归并排序

这题其实在算法导论里就有提到。

以下内容来自liweiwei

  • 利用「归并排序」计算逆序对,是非常经典的做法;
  • 关键在于「合并两个有序数组」的步骤,利用数组的部分有序性,一下子计算出一个数之前或者之后元素的逆序的个数;
  • 前面「分」的时候什么都不做,「合」的过程中计算「逆序对」的个数
  • 「排序」的工作是必要的,正是因为「排序」才能在下一轮利用顺序关系加快逆序数的计算,也能避免重复计算;
  • 在代码实现上,只需要在「归并排序」代码的基础上,加上「逆序对」个数的计算,计算公式需要自己在草稿纸上推导。

所有的「逆序对」来源于 3 个部分:

  • 左边区间的逆序对;
  • 右边区间的逆序对;
  • 横跨两个区间的逆序对。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
public class Solution {

public int reversePairs(int[] nums) {
int len = nums.length;

if (len < 2) {
return 0;
}

int[] copy = new int[len];
for (int i = 0; i < len; i++) {
copy[i] = nums[i];
}

int[] temp = new int[len];
return reversePairs(copy, 0, len - 1, temp);
}

/**
* nums[left..right] 计算逆序对个数并且排序
*
* @param nums
* @param left
* @param right
* @param temp
* @return
*/
private int reversePairs(int[] nums, int left, int right, int[] temp) {
if (left == right) {
return 0;
}

int mid = left + (right - left) / 2;
int leftPairs = reversePairs(nums, left, mid, temp);
int rightPairs = reversePairs(nums, mid + 1, right, temp);

// 如果整个数组已经有序,则无需合并,注意这里使用小于等于
if (nums[mid] <= nums[mid + 1]) {
return leftPairs + rightPairs;
}

int crossPairs = mergeAndCount(nums, left, mid, right, temp);
return leftPairs + rightPairs + crossPairs;
}

/**
* nums[left..mid] 有序,nums[mid + 1..right] 有序
*
* @param nums
* @param left
* @param mid
* @param right
* @param temp
* @return
*/
private int mergeAndCount(int[] nums, int left, int mid, int right, int[] temp) {
for (int i = left; i <= right; i++) {
temp[i] = nums[i];
}

int i = left;
int j = mid + 1;

int count = 0;
for (int k = left; k <= right; k++) {
// 有下标访问,得先判断是否越界
if (i == mid + 1) {
nums[k] = temp[j];
j++;
} else if (j == right + 1) {
nums[k] = temp[i];
i++;
} else if (temp[i] <= temp[j]) {
// 注意:这里是 <= ,写成 < 就不对,请思考原因
nums[k] = temp[i];
i++;
} else {
nums[k] = temp[j];
j++;

// 在 j 指向的元素归并回去的时候,计算逆序对的个数,只多了这一行代码
count += (mid - i + 1);
}
}
return count;
}
}