在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5
限制:
0 <= 数组长度 <= 50000
1、暴力解法(超时):
这是我最开始想到的方法,花了1分钟写完执行了一下超时,我就说标着困难的题目没这么简单吧,哎,换种思路吧。
代码(超时):
public class Leetcode面试题51 {
static int reversePairs(int[] nums) {
if (nums.length <= 1) {
return 0;
}
int res = 0;
for (int i = 0; i < nums.length - 1; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[i] > nums[j]) {
res++;
}
}
}
return res;
}
public static void main(String[] args) {
System.out.println(reversePairs(new int[]{7,5,6,4}));
}
}
2、分治思想
计算逆序数就发生在排序的过程中,利用了「排序」以后数组的有序性。
Java AC代码:
public class Leetcode面试题51 {
static int reversePairs(int[] nums) {
int len = nums.length;
if (len < 2) {
return 0;
}
int[] numsCopy = new int[len];
for (int i = 0; i < len; i++) {
numsCopy[i] = nums[i];
}
int[] temp = new int[len];
return reversePairs(numsCopy, 0, len - 1, temp);
}
//计算逆序对个数并且排序
static int reversePairs(int[] nums, int left, int right, int[] temp) {
if (left == right) {
return 0;
}
int mid = left + (right - left) / 2;
int leftRes = reversePairs(nums, left, mid, temp);
int rightRes = reversePairs(nums, mid + 1, right, temp);
if (nums[mid] <= nums[mid + 1]) {
return leftRes + rightRes;
}
int crossRes = mergeAndCount(nums, left, mid, right, temp);
return leftRes + rightRes + crossRes;
}
//计算crossRes(交叉)
static 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++;
count += (mid - i + 1);
}
}
return count;
}
public static void main(String[] args) {
System.out.println(reversePairs(new int[]{7,5,6,4}));
}
}
3、树状数组
这种方法是我看解题思路看到的,具体的做法是:
这个解法我看了蛮久的,还是不会,感觉自己还是太菜了,大家可以参考一下我贴的大佬的代码:
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
//作者:liweiwei1419
//链接:https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/solution/bao-li-jie-fa-fen-zhi-si-xiang-shu-zhuang-shu-zu-b/
public class Solution {
public int reversePairs(int[] nums) {
int len = nums.length;
if (len < 2) {
return 0;
}
// 离散化:使得数字更紧凑,节约树状数组的空间
// 1、使用二分搜索树是为了去掉重复元素
Set<Integer> treeSet = new TreeSet<>();
for (int i = 0; i < len; i++) {
treeSet.add(nums[i]);
}
// 2、把排名存在哈希表里方便查询
Map<Integer, Integer> rankMap = new HashMap<>();
int rankIndex = 1;
for (Integer num : treeSet) {
rankMap.put(num, rankIndex);
rankIndex++;
}
int count = 0;
// 在树状数组内部完成前缀和的计算
// 规则是:从后向前,先给对应的排名 + 1,再查询前缀和
FenwickTree fenwickTree = new FenwickTree(rankMap.size());
for (int i = len - 1; i >= 0; i--) {
int rank = rankMap.get(nums[i]);
fenwickTree.update(rank, 1);
count += fenwickTree.query(rank - 1);
}
return count;
}
private class FenwickTree {
private int[] tree;
private int len;
public FenwickTree(int n) {
this.len = n;
tree = new int[n + 1];
}
/**
* 单点更新:将 index 这个位置 + delta
*
* @param i
* @param delta
*/
public void update(int i, int delta) {
// 从下到上,最多到 size,可以等于 size
while (i <= this.len) {
tree[i] += delta;
i += lowbit(i);
}
}
// 区间查询:查询小于等于 tree[index] 的元素个数
// 查询的语义是「前缀和」
public int query(int i) {
// 从右到左查询
int sum = 0;
while (i > 0) {
sum += tree[i];
i -= lowbit(i);
}
return sum;
}
public int lowbit(int x) {
return x & (-x);
}
}
}
2020.4.24打卡