> 文章列表 > 【LeetCode】剑指 Offer 51. 数组中的逆序对 p249 -- Java Version

【LeetCode】剑指 Offer 51. 数组中的逆序对 p249 -- Java Version

【LeetCode】剑指 Offer 51. 数组中的逆序对 p249 -- Java Version

题目链接:https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/

1. 题目介绍(51. 数组中的逆序对)

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

【LeetCode】剑指 Offer 51. 数组中的逆序对 p249 -- Java Version

【测试用例】:
示例 1:

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

【条件约束】:

限制

  • 0 <= 数组长度 <= 50000

2. 题解

2.1 枚举 – O(n2)

时间复杂度O(n2),空间复杂度O(1)

解题思路】:
该题最直接的思路就是 枚举顺序扫描整个数组,每扫描到一个数字,逐个比较该数字和它后面的数字的大小。如果后面的数字比它小,则这两个数字就组成一个逆序对。假设数组中含有 n 个数字。由于每个数字都要和 O(n) 个数字进行比较,因此这种算法的时间复杂度是 O(n2)
……
实现策略】:

  1. 定义变量 n,用来记录输入数组长度,然后以此进行无效输入判断;
  2. 定义变量 count,用来记录这个数组中逆序对的总数;
  3. 实现双重循环,让每个数字都与其后面的所有数字进行比较;
  4. 循环结束,返回 count.
class Solution {// Soultion1:枚举public int reversePairs(int[] nums) {// 定义变量 n,用来记录输入数组长度int n = nums.length;// 无效输入判断if (n <= 1) return 0;// 定义变量 count,用来记录这个数组中逆序对的总数int count = 0;// 双重循环,让每个数字都与其后面的所有数字进行比较for (int i = 0; i < n; i++) {for (int j = i+1; j < n; j++) {if (nums[i] > nums[j]) count++;}}// 循环结束,返回结果return count;}
}

【LeetCode】剑指 Offer 51. 数组中的逆序对 p249 -- Java Version

2.2 归并排序 – O(nlogn)

时间复杂度O(nlogn),空间复杂度O(n)

解题思路】:
以数组 {7,5,6,4} 为例来分析统计逆序对的过程。每扫描到一个数字的时候,我们不能拿它和后面的每一个数字进行比较,否则时间复杂度就是
O(n2),因此我们可以 考虑先比较两个相邻的数字
……
依据这种思想,我们可以将数组进行以下四步的操作,即:

  • 拆分
  • 合并
  • 统计
  • 排序

其中合并和统计可以一块进行。
……
而这个排序的过程实际上就是 归并排序,归并排序可参考:【算法】排序算法之归并排序
……
合并过程可参考:
【LeetCode】剑指 Offer 51. 数组中的逆序对 p249 -- Java Version

  • 合并阶段 本质上是 合并两个排序数组 的过程,而每当遇到 左子数组当前元素 > 右子数组当前元素 时,意味着 「左子数组当前元素 至 末尾元素」 与 「右子数组当前元素」 构成了若干 「逆序对」,同时,我们要将较小的那个元素存入临时数组中

……
实现策略】:

  1. 利用数组长度 n 进行无效输入判断;
  2. 对数组 nums 进行归并排序;
    • 递归划分左右区域;
    • 合并、判断、排序;

【LeetCode】剑指 Offer 51. 数组中的逆序对 p249 -- Java Version

class Solution {// Soultion2:归并排序int[] nums;public int reversePairs(int[] nums) {// 定义变量 n,用来记录输入数组长度int n = nums.length;// 无效输入判断if (n <= 1) return 0;// 初始化数组this.nums = nums;// 定义变量 count,用来记录这个数组中逆序对的总数int count = mergeSort(nums,0,n-1);// 循环结束,返回结果return count;}public int mergeSort(int[] nums,int l,int r){// 当只有一个节点的时候,直接返回,退出递归if(l >= r) return 0;// 递归划分int mid = l + (r - l) / 2;// 左拆分int left = mergeSort(nums,l,mid);// 右拆分int right = mergeSort(nums,mid+1,r);// 合并int count = merge(nums,l,mid,r);// 返回最终结果return left + right + count;}public int merge(int[] nums,int left,int mid,int right){int count = 0;// 定义一个临时数组int[] temp = new int[right-left+1];// 定义一个指针,指向第一个数组的第一个元素int i = left;// 定义一个指针,指向第二个数组的第一个元素int j = mid+1;// 定义一个指针,指向临时数组的第一个元素int t = 0;// 当两个数组都有元素的时候,遍历比较每个元素大小while(i <= mid && j <= right){// 比较两个数组的元素,取较小的元素加入到,临时数组中// 并将两个指针指向下一个元素if(nums[i] <= nums[j]){temp[t++] = nums[i++];}else{// 当左边数组的大与右边数组的元素时,就对当前元素以及后面的元素的个数进行统计,// 此时这个数就是,逆序数// 定义一个计数器,记下每次合并中存在的逆序数。count += mid-i+1;temp[t++] = nums[j++];}}// 当左边的数组没有遍历完成后,直接将剩余元素加入到临时数组中while(i <= mid){temp[t++] = nums[i++];}// 当右边的数组没有遍历完成后,直接将剩余元素加入到临时数组中while(j <= right){temp[t++] =nums[j++];}// 将新数组中的元素,覆盖nums旧数组中的元素。// 此时数组的元素已经是有序的for(int k =0; k< temp.length;k++){nums[left+k] = temp[k];}return count;}
}

【LeetCode】剑指 Offer 51. 数组中的逆序对 p249 -- Java Version

3. 参考资料

[1] 剑指 Offer 51. 数组中的逆序对(归并排序,清晰图解)
[2] 排序——归并排序(Merge sort)