2018-03-26 22:02:36
逆序对定义:
对于一个包含N个非负整数的数组A[1..n],如果有i < j,且A[ i ]>A[ j ],则称(A[ i] ,A[ j] )为数组A中的一个逆序对。例如,数组(3,1,4,5,2)的逆序对有(3,1),(3,2),(4,2),(5,2),共4个。
逆序对问题就是求解一个数组中的逆序对的个数,逆序对问题是个非常经典的问题。当然可以写出一个在O(n ^ 2)时间复杂度解决的算法,这里就不对这种naive的解法做介绍了,主要会讲解两种O(nlogn)的解法。
一、树状数组
通过树状数组求解逆序对是个很经典的解法,树状数组的特点是单点更新,区间求和,使用树状数组求解逆序对从本质来说就是一个求和问题,我们可以为每个数字构建一个桶,出现了就在该桶里的数目上加1,可以倒序遍历数组,当遍历到某个数的时候,求getsum(nums[i]),即求解i位后的比a[i]小的数的个数,最后求总和就是结果。
桶排序的问题,这里也会出现,也就是数组中的区间范围如果非常庞大,那么建立max + 1大小的桶就非常的不合算,这时候就需要使用离散化的技术进行预处理。
使用树状数组的时间复杂度为O(nlogn)。
- 离散化
离散化,把无限空间中有限的个体映射到有限的空间中去,以此提高算法的时空效率。
通俗的说,离散化是在不改变数据相对大小的条件下,对数据进行相应的缩小。例如:
原数据:1,999,100000,15;处理后:1,3,4,2;
注意到,离散化后的数组中的逆序对和原数组是相等的。
在C++中可以借助STL进行离散化:
思路是:先排序,再删除重复元素,最后就是索引元素离散化后对应的值。假定待离散化的序列为a[n],sub_a[n]是序列a[n]的一个副本,则对应以上三步为:sort(sub_a,sub_a+n);int size=unique(sub_a,sub_a+n)-sub_a;//size为离散化后元素个数for(i=0;i<n;i++)a[i]=lower_bound(sub_a,sub_a+size,a[i])-sub_a + 1;
- 树状数组求解问题
public List<Integer> countSmaller(int[] nums) {
List<Integer> res = new ArrayList<>();
int[] sorted = Arrays.copyOf(nums, nums.length);
Arrays.sort(sorted);
Map<Integer, Integer> ranks = new HashMap<>();
int rank = 0;
for (int i = 0; i < sorted.length; ++i)
if (i == 0 || sorted[i] != sorted[i - 1])
ranks.put(sorted[i], ++rank);
int[] bit = new int[ranks.size() + 1];
for (int i = nums.length - 1; i >= 0; --i) {
int sum = query(bit, ranks.get(nums[i]) - 1);
res.add(sum);
update(bit, ranks.get(nums[i]), 1);
}
Collections.reverse(res);
return res;
}
private int query(int[] bit, int i) {
int res = 0;
for (int k = i; k > 0; k -= (k & -k)) {
res += bit[k];
}
return res;
}
private void update(int[] bit, int i, int delta) {
for (int k = i; k < bit.length; k += (k & -k)) {
bit[k] += delta;
}
}
二、归并排序
归并排序在merge的过程中天然会进行前后数的比较,因此使用归并排序来求解逆序对问题就水到渠成了。
时间复杂度:
O(nlogn)。
public class MergeSort {
int merge(int[] nums, int[] tmp, int L, int R, int REnd) {
int res = 0;
int index = L;
int len = REnd - L + 1;
int LEnd = R - 1;
while (L <= LEnd && R <= REnd) {
if (nums[R] < nums[L]) {
res += LEnd - L + 1;
tmp[index++] = nums[R++];
}
else tmp[index++] = nums[L++];
}
while (L <= LEnd) tmp[index++] = nums[L++];
while (R <= REnd) tmp[index++] = nums[R++];
for (int i = 0; i < len; i++, REnd--) {
nums[REnd] = tmp[REnd];
}
return res;
}
int mergeSort(int[] nums, int[] tmp, int L, int REnd) {
if (L < REnd) {
int mid = (REnd - L) / 2 + L;
return mergeSort(nums, tmp, L, mid) + mergeSort(nums, tmp, mid + 1, REnd) +
merge(nums, tmp, L, mid + 1, REnd);
}
return 0;
}
public static void main(String[] args) {
int[] nums = new int[]{2, 3, 8, 6, 1};
MergeSort ms = new MergeSort();
System.out.println(ms.mergeSort(nums, new int[nums.length], 0, nums.length - 1));
}
}