在问 · 算法题解

493. 翻转对

题目描述

输入整数数组 nums。

若下标 i < j 且 nums[i] > 2 * nums[j],就称 (i, j) 是一个重要翻转对。

返回数组中重要翻转对的总数。

示例 1
输入:nums = [1,3,2,3,1]
输出:2

解释:下标对 (1,4) 和 (3,4) 满足条件:两个 3 都大于 2 * 1。

示例 2
输入:nums = [2,4,3,5,1]
输出:3

解释:(1,4)、(2,4)、(3,4) 满足条件:4、3、5 都大于 2 * 1。

数据范围

  • 数组长度不超过 50000。
  • 数组中所有数都在 32 位有符号整数范围内。

解题思路:归并排序

归并分治:把 [lo, hi) 分成左右两半,各自递归计数并排好序。跨越两半的对子里,i 在左半、j 在右半,下标顺序自动满足。由于两半都有序,对左半的 nums[i] 从小到大枚举,右半中满足 nums[i] > 2·nums[j] 的 j 是一段前缀,并且这段前缀只会变长。所以用一个不回退的指针 j 求出前缀长度 j - mid(也可以对每个 i 在右半二分)。计完数再把整段合并排序,交给上一层。

为什么正确

sort(lo, hi) 返回时,nums[lo:hi] 已有序,返回值是这段内部的翻转对数。

复杂度分析

每层双指针 O(n),共 log n 层;用真正的归并合并时总时间 O(n log n)(本解用库排序合并,是 O(n log² n)),空间 O(n)。

易错点

2 * nums[j] 在 32 位下会溢出,C++/Java 要转成 64 位再乘。先计数再合并,不能把计数条件套在合并的比较上(两者条件不同)。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def reversePairs(self, nums: List[int]) -> int:
        def sort(lo, hi):
            if hi - lo <= 1:
                return 0
            mid = (lo + hi) // 2
            cnt = sort(lo, mid) + sort(mid, hi)
            j = mid
            for i in range(lo, mid):
                while j < hi and nums[i] > 2 * nums[j]:
                    j += 1
                cnt += j - mid
            nums[lo:hi] = sorted(nums[lo:hi])
            return cnt
        return sort(0, len(nums))

载入编辑器,自己跑一遍 →

其他解法

归并排序在合并前用双指针统计跨两半的翻转对,离线分治;树状数组按顺序逐个统计,需要离散化,但思路是在线的。

解法 2:离散化 + 树状数组 时间 O(n log n),空间 O(n)

把所有 nums[i] 和 2·nums[i] 排序去重,映射成 1~m 的秩。从左到右扫描,树状数组记录已经出现过的数。对当前 x,查询前面不超过 2x 的个数,用已扫描个数减去它,就是前面大于 2x 的个数,累加进答案。然后把 x 插入树状数组。

在线逐个处理,不修改原数组顺序,模板可复用于各种“统计前面比我大/小的数”问题。需要离散化,并注意 2x 超出 32 位整数范围。

class Solution:
    def reversePairs(self, nums: List[int]) -> int:
        # 离散化:把所有 nums[i] 和 2*nums[i] 映射到 1..m
        vals = sorted(set(nums) | {2 * x for x in nums})
        rank = {v: i + 1 for i, v in enumerate(vals)}
        m = len(vals)
        tree = [0] * (m + 1)

        def add(i):
            while i <= m:
                tree[i] += 1
                i += i & -i

        def query(i):  # 已插入的数中秩 <= i 的个数
            s = 0
            while i > 0:
                s += tree[i]
                i -= i & -i
            return s

        ans = 0
        for j, x in enumerate(nums):
            # 前面 j 个数中大于 2x 的个数
            ans += j - query(rank[2 * x])
            add(rank[x])
        return ans

二分查找 专题的其他题目

查看 二分查找 全部题目 →