493. 翻转对

422 阅读2分钟

题目描述

给定一个数组 nums ,如果 i < j 且 nums[i] > 2*nums[j] 我们就将 (i, j) 称作一个重要翻转对。

你需要返回给定数组中的重要翻转对的数量。

解题思路

算法

归并算法,合并时进行翻转对的统计

思路

利用归并排序在合并时左,右区间都是升序排列的特点进行翻转对统计,这样既方便而且不会重复,因为在合并之后两个区间会作为整体来和其他区间进行比较

过程

合并过程中,令左区间的指针为 p1, 右为 p2,根据合并排序有 l <= p1 <= mid, mid + 1 <= p2 <= r,其中 l 为左边界,r 为右边界,mid 是划分左区间和右区间的边界

其中:

左区间[l, mid]
右区间[mid + 1, r]

在合并之前,通过 while 循环统计翻转对个数

统计过程中,检查每个 nums[p1] 是否和当前的 nums[p2] 符合翻转对的要求,如果不符合 p1++,否则做一次统计之后 p2++

具体过程是:

通过递归分别统计划分的左区间和右区间,并记录到统计结果 ans

当左区间遍历到 nums[p1],右区间合并到 nums[p2] 时,如果此时符合翻转对的要求,即 nums[p1] > 2 * nums[p2],因为左区间是递增的,所以 p1 之后的所有左区间元素也都符合条件,因此我们要把统计结果 ans 加上从 p1 到左区间右边界 mid 的长度,即 mid - p1 + 1

记录后,继续检查下一个右区间元素,让 p2 递增

如果当前遍历到的 nums[p1] 不符合要求,直接递增 p1

特别注意

与统计逆序对不同,我们不能在合并 p2 元素的时候才统计翻转对,因为符合要求的左区间元素很有可能已经被合并,这是因为:

合并元素之要求比大小,而翻转对则要求比较相差的倍数

代码

/**
 * @param {number[]} nums
 * @return {number}
 */
var reversePairs = function (nums) {
  return countResult(nums, 0, nums.length - 1)
}

function countResult(nums, l, r) {
  // if the section length <= 1, reverse pairs number is 0
  if (l >= r) return 0
  let ans = 0
  const mid = (l + r) >> 1,
    temp = []

  // pairs in the left section
  ans += countResult(nums, l, mid)
  // pairs in the right section
  ans += countResult(nums, mid + 1, r)

  // pairs across the left & right
  let k = 0,
    p1 = l,
    p2 = mid + 1

  while (p1 <= mid && p2 <= r) {
    if (nums[p1] > 2 * nums[p2]) {
      ans += mid - p1 + 1
      p2++
    } else {
      p1++
    }
  }

  p1 = l
  p2 = mid + 1
  while (p1 <= mid || p2 <= r) {
    if (p2 > r || (p1 <= mid && nums[p1] < nums[p2])) {
      temp[k++] = nums[p1++]
    } else {
      temp[k++] = nums[p2++]
    }
  }

  for (let i = l; i <= r; i++) {
    nums[i] = temp[i - l]
  }
  return ans
}