4、翻转对

厨子大约 3 分钟数据结构算法算法基地面试排序算法秒杀

题目描述

leetcode 493 翻转对open in new window

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

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

示例 1:

输入: [1,3,2,3,1] 输出: 2

示例 2:

输入: [2,4,3,5,1] 输出: 3

题目解析

我们理解了逆序对的含义之后,题目理解起来完全没有压力的,这个题目第一想法可能就是用暴力法解决,但是会超时,所以我们有没有办法利用归并排序来完成呢?

我们继续回顾一下归并排序的归并过程,两个小集合是有序的,然后我们需要将小集合归并到大集合中,则我们完全可以在归并之前,先统计一下翻转对的个数,然后再进行归并,则最后排序完成之后自然也就得出了翻转对的个数。具体过程见下图。

翻转对
翻转对

此时我们发现 6 > 2 * 2,所以此时是符合情况的,因为小数组是单调递增的,所以 6 后面的元素都符合条件,所以我们 count += mid - temp1 + 1;则我们需要移动紫色指针,判断后面是否还存在符合条件的情况。

翻转对
翻转对

我们此时发现 6 = 3 * 2,不符合情况,因为小数组都是完全有序的,所以我们可以移动红色指针,看下后面的数有没有符合条件的情况。这样我们就可以得到翻转对的数目啦。下面我们直接看动图加深下印象吧!

动画模拟

是不是很容易理解啊,那我们直接看代码吧,仅仅是在归并排序的基础上加了几行代码。

代码

Java Code:

class Solution {
    private int count;

    public int reversePairs(int[] nums) {
        count = 0;
        merge(nums, 0, nums.length - 1);
        return count;
    }

    public void merge(int[] nums, int left, int right) {

        if (left < right) {
            int mid = left + ((right - left) >> 1);
            merge(nums, left, mid);
            merge(nums, mid + 1, right);
            mergeSort(nums, left, mid, right);
        }

    }

    public void mergeSort(int[] nums, int left, int mid, int right) {

        int[] temparr = new int[right - left + 1];
        int temp1 = left, temp2 = mid + 1, index = 0;
        //计算翻转对
        while (temp1 <= mid && temp2 <= right) {
            //这里需要防止溢出
            if (nums[temp1] > 2 * (long) nums[temp2]) {
                count += mid - temp1 + 1;
                temp2++;
            } else {
                temp1++;
            }
        }
        //记得归位,我们还要继续使用
        temp1 = left;
        temp2 = mid + 1;
        //归并排序
        while (temp1 <= mid && temp2 <= right) {

            if (nums[temp1] <= nums[temp2]) {
                temparr[index++] = nums[temp1++];
            } else {
                temparr[index++] = nums[temp2++];
            }
        }
        //照旧
        if (temp1 <= mid) System.arraycopy(nums, temp1, temparr, index, mid - temp1 + 1);
        if (temp2 <= right) System.arraycopy(nums, temp2, temparr, index, right - temp2 + 1);
        System.arraycopy(temparr, 0, nums, left, right - left + 1);
    }
}

Python Code:

from typing import List
class Solution:
    count = 0
    def reversePairs(self, nums: List[int])->int:
        self.count = 0
        self.merge(nums, 0, len(nums) - 1)
        return self.count

    def merge(self, nums: List[int], left: int, right: int):
        if left < right:
            mid = left + ((right - left) >> 1)
            self.merge(nums, left, mid)
            self.merge(nums, mid + 1, right)
            self.mergeSort(nums, left, mid, right)

    def mergeSort(self, nums: List[int], left: int, mid: int, right: int):

        temparr = [0] * (right - left + 1)
        temp1 = left
        temp2 = mid + 1
        index = 0
        while temp1 <= mid and temp2 <= right:
            # 这里需要防止溢出
            if nums[temp1] > 2 * nums[temp2]:
                self.count += mid - temp1 + 1
                temp2 += 1
            else:
                temp1 += 1

        # 记得归位,我们还要继续使用
        temp1 = left
        temp2 = mid + 1
        # 归并排序
        while temp1 <= mid and temp2 <= right:
            if nums[temp1] <= nums[temp2]:
                temparr[index] = nums[temp1]
                index += 1
                temp1 += 1
            else:
                temparr[index] = nums[temp2]
                index += 1
                temp2 += 1
        # 照旧
        if temp1 <= mid:
             temparr[index: index + mid - temp1 + 1] = nums[temp1: temp1 + mid - temp1 + 1]
        if temp2 <= right:
            temparr[index: index + right - temp2 + 1] = nums[temp2: temp2 + right - temp2 + 1]
        nums[left: left + right- left + 1] = temparr[0: right - left + 1]