leetcode刷题_剑指 Offer 51. 数组中的逆序对

157 阅读4分钟

「这是我参与2022首次更文挑战的第11天,活动详情查看:2022首次更文挑战

题目

剑指 Offer 51. 数组中的逆序对

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

 示例 1:

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

解法一

思路

暴力,就是凎。

当然啦,结局是过不了,时间超限制了。但也是一种思路嘛

/**
* @param {number[]} nums
* @return {number}
*/

var reversePairs = function(nums) {
	let len = nums.length;
	let count = 0;
	for(let i=0;i<len-1;i++){
		for(let j=i+1;j<len;j++){
			if(nums[i]>nums[j]){
				count++;
			}
		}
	}
	return count
};

复杂度分析

时间复杂度:O(n2)

空间复杂度:O(1)

解法二

思路:

归并排序。

还记得我们归并排序是怎么做的么,不记得可以戳这里我不信你看不懂我这篇归并排序

那么我先举个例子,如果去结合归并排序计算逆序对

假设有两个已经排序好的数组arr1 = [2,5,8,9],arr2 = [3,6,7,10]

然后我们有个空数组M = [ ],这个空数组用来装arr2里比较后较小的数(怎么比较我们后面说,莫慌)

我们这样来统计逆序对

  1. 用arr1里第一个数2,去和arr2里第一数3比较,2更小,将2从arr1中拿出不要了。这时候arr1 = [5,8,9],arr2 = [3,6,7,10],M = [ ];
  2. 继续用arr1里第一个数5,去和arr2里第一数3比较,显然3小,将3拿出放入M中。这时候arr1 = [5,8,9],arr2 = [6,7,10],M = [ 3];
  3. 继续用arr1里第一个数5,去和arr2里第一数6比较,这时候5更小,将5拿出不要了。这时候注意了,M中的数肯定都比5小,那么5是不是可以和M中所有的数都构成逆序对?此时M中只有一个3,所以构成1个逆序对,逆序对数量+1。这时候arr1 = [8,9],arr2 = [6,7,10],M = [ 3];
  4. 继续用arr1里第一个数8,去和arr2里第一数6比较,这时候6更小,将6拿出放入M中。这时候arr1 = [8,9],arr2 = [7,10],M = [ 3,6];
  5. 继续用arr1里第一个数8,去和arr2里第一数7比较,这时候7更小,将7拿出放入M中。这时候arr1 = [8,9],arr2 = [10],M = [ 3,6,7];
  6. 继续用arr1里第一个数8,去和arr2里第一数10比较,这时候8更小,将8拿出不要了。这时候M中的数肯定都比8小,那么8是不是可以和M中所有的数都构成逆序对?此时M中有3,6,7,所以构成3个逆序对,逆序对数量+1。这时候arr1 = [9],arr2 = [10],M = [ 3,6,7];

那么规律是不是已经找到了

每从左边数组拿出一个数,就去看看之前右边数组拿出了几个数,有几个就可以构成几个逆序对。

那么这个规律可以很好的和我们归并排序结合起来:

  1. 每次做合并操作的时候,如果从左边的数组拿出数放入result的时候,就去看一下右边数组此时拿出了几个数,有几个数就把逆序对+ 几。
  2. 那我们其实只要在归并排序的代码上稍微修改一下,加上一个数量统计即可。

再看看我们归并排序的图,用上面的规律去算一下合并过程的逆序对

我已经把合并过程中的逆序对统计,用小数字标记了,总共是7个逆序对

归并排序 (1).png

代码如下:

/**
 * @param {number[]} nums
 * @return {number}
 */
var reversePairs = function(nums) {
    let len = nums.length;
    let result = [];
    result.count = 0;
    dfs(nums, result, 0, len-1)
    return result.count;
};

function dfs(nums, result, start, end) {
    if(start >= end) return;
    let block = end - start + 1;
    let mid = Math.ceil(block/2) + start - 1;
    // all in [start,mid] is left
    // all in [mid+1,end] is right
    let start1 = start;
    let end1 = mid;
    let start2 = mid + 1;
    let end2 = end;
    dfs(nums, result, start1, end1);
    dfs(nums, result, start2, end2);
    let k = start;
    let useBlock2 = 0;
    while(start1 <= end1 && start2 <= end2){
        if(nums[start1] <= nums[start2]){
            result[k] = nums[start1];
            result.count += useBlock2;
            start1++;
            k++
        } else {
            result[k] = nums[start2];
            useBlock2++;
            start2++;
            k++;
        }
    }
    while(start1 <= end1){
        result[k] = nums[start1];
        result.count += useBlock2;
        start1++;
        k++
    }
    while(start2 <= end2){
        result[k] = nums[start2];
        start2++;
        k++
    }
    for(let i=start;i<=end;i++){
        nums[i] = result[i];
    }
}

复杂度分析

时间复杂度:O(nlogn),同归并排序

空间复杂度:O(n),同归并排序