当前位置: 首页 > news >正文

(每日一道算法题)翻转对

493. 翻转对 - 力扣(LeetCode)

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

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

示例 1:

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

示例 2:

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

注意:

  1. 给定数组的长度不会超过50000
  2. 输入数组中的所有数字都在32位整数的表示范围内。

暴力解法与优化思路

1. 暴力解法(不可行)

直接遍历所有 i < j 的组合,检查条件 nums[i] > 2 * nums[j]。时间复杂度为 O(n^2),在 n=50000 时无法通过。

2. 归并排序的分治优化

归并排序的核心思想是 ​分而治之

  1. :将数组递归拆分为左右子数组。
  2. :分别对左右子数组排序。
  3. :合并两个有序子数组。

在合并过程中,可以利用 ​子数组的有序性 高效统计逆序对。


为什么普通逆序对可以直接统计,而重要翻转对不行?

普通逆序对(nums[i] > nums[j]

  • 合并阶段统计:当左右子数组为升序时,若 nums[i] > nums[j],则左子数组后续元素均大于 nums[j],可立即统计剩余元素数量。
  • 时间复杂度O(n log n)

重要翻转对(nums[i] > 2 * nums[j]

  • 无法直接合并统计:即使左右子数组有序(如升序),也无法保证 nums[i] > 2 * nums[j] 的后续元素满足条件。
  • 独立统计的必要性:必须 ​单独遍历有序子数组,利用有序性快速统计。

归并排序的优化实现

关键步骤

  1. 降序排序:保证左右子数组为降序排列。
  2. 独立统计阶段:合并前,遍历左右子数组,统计满足条件的对数。
  3. 合并阶段:仅合并,不统计。

代码实现

 

class Solution {
    private int[] tmp;
    private int count;

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

    private void mergeSort(int[] nums, int left, int right) {
        if (left >= right) return;
        int mid = left + (right - left) / 2;
        mergeSort(nums, left, mid);
        mergeSort(nums, mid + 1, right);
        countPairs(nums, left, mid, right); // 独立统计阶段
        merge(nums, left, mid, right);      // 合并为降序数组
    }

    // 统计重要翻转对(左右子数组已降序)
    private void countPairs(int[] nums, int left, int mid, int right) {
        int i = left, j = mid + 1;
        while (i <= mid) {
            // 右子数组降序,找到第一个不满足 nums[j] >=nums[j]/2.0 的 j
            while (j <= right && nums[j] >=nums[i]/2.0) {
                j++;
            }
            count += right-j+1; // 统计满足条件的数量
            i++;
        }
    }

    // 降序合并两个子数组
    private void merge(int[] nums, int left, int mid, int right) {
        int i = left, j = mid + 1, k = 0;
        while (i <= mid && j <= right) {
            if (nums[i] >= nums[j]) {
                tmp[k++] = nums[i++];
            } else {
                tmp[k++] = nums[j++];
            }
        }
        while (i <= mid) tmp[k++] = nums[i++];
        while (j <= right) tmp[k++] = nums[j++];
        System.arraycopy(tmp, 0, nums, left, right - left + 1);
    }
}

代码解析

1. 独立统计阶段 countPairs

  • 输入:已排序的左右子数组(降序)。
  • 双指针遍历
    • 左指针 i 遍历左子数组。
    • 右指针 j 从右子数组起始位置开始,找到第一个不满足 nums[i] > 2 * nums[j] 的位置。
    • 统计 j 之前的元素数量(均满足条件)。

2. 合并阶段 merge

  • 降序合并:将左右子数组合并为一个降序数组,为后续操作提供有序性保证。

3. 复杂度分析

  • 时间复杂度O(n log n),归并排序的递归深度为 log n,每层操作复杂度为 O(n)
  • 空间复杂度O(n),用于临时数组 tmp

关键点总结

  1. 降序排序的必要性:保证右子数组元素从前往后递增,使得统计时能利用有序性快速定位。
  2. 独立统计阶段:必须在合并前单独统计,避免遗漏可能的翻转对。
  3. 防止整数溢出:比较时使用 long 类型(如 2L * nums[j])。

文章转载自:
http://accidental.wkuuf.cn
http://archipelago.wkuuf.cn
http://afterdeck.wkuuf.cn
http://beaky.wkuuf.cn
http://bachelorette.wkuuf.cn
http://beyrouth.wkuuf.cn
http://bacteriolysis.wkuuf.cn
http://artal.wkuuf.cn
http://autoanalysis.wkuuf.cn
http://beeb.wkuuf.cn
http://boggle.wkuuf.cn
http://aural.wkuuf.cn
http://avionics.wkuuf.cn
http://bickiron.wkuuf.cn
http://battlemented.wkuuf.cn
http://banality.wkuuf.cn
http://chingkang.wkuuf.cn
http://bragi.wkuuf.cn
http://armet.wkuuf.cn
http://azonic.wkuuf.cn
http://acetic.wkuuf.cn
http://babysitter.wkuuf.cn
http://antrorsely.wkuuf.cn
http://backplane.wkuuf.cn
http://anthurium.wkuuf.cn
http://chancel.wkuuf.cn
http://analyst.wkuuf.cn
http://atrous.wkuuf.cn
http://barreled.wkuuf.cn
http://burstone.wkuuf.cn
http://www.dtcms.com/a/86653.html

相关文章:

  • 基于 ECharts 实现动态图表渲染支持10万+数据点实时更新方案
  • Flutter使用自签证书打包ipa
  • Excel 小黑第19套
  • 基于springboot的美容院管理系统(044)
  • 深圳问顶安全科技有限公司asktopsec是做什么的?
  • 事件、页面跳转、wxml语法——微信小程序学习笔记
  • 【AI】Orin Nano+ubuntu22.04上移植YoloV11,并使用DeepStream测试成功
  • How to share files with Windows via samba in Linux mint 22
  • MyBatisPlus(SpringBoot版)学习第二讲:基本CRUD
  • 安装PrettyZoo操作指南
  • JavaEE进阶---Mybatis(预编译SQL即时SQL动态SQL标签池化技术说明)
  • 堆的相关要点以及模拟实现
  • 快速部署Samba共享服务器作为k8s后端存储
  • 透析主流CSS预处理器的区别
  • 红蓝队自动化项目资产侦察武器库部署企查产权网络空间
  • Excel(实战):INDEX函数和MATCH函数、INDEX函数实战题
  • TDengine 中的 show 命令
  • Scratch游戏 | 《拍苍蝇》——Scratch厨房清洁大作战!
  • 【NLP 42、实践 ⑪ 用Bert模型结构实现自回归语言模型的训练】
  • centos7搭建postgresql12主从
  • Node.js 格式化时间的两种方法:原生 Date 与 npm 包 moment 详解
  • [运维]Windows 开启 Hyper-V 之后上传网速变慢解决方案
  • 2025清华大学:DeepSeek教程全集(PDF+视频精讲,共10份).zip
  • Spring MVC 执行流程:一个请求在 Spring MVC 中是如何执行的?
  • css的背景
  • Linux Namespace(网络命名空间)系列二 --- 使用 Open vSwitch 和网络命名空间搭建虚拟网络
  • 前后端开发中,图片上传到不同格式(二进制数据ORbase64编码)
  • nuxt项目 详情页有阅读次数需要更新,有热门推荐列表需要更新适合做SSG吗
  • JSON相关的Java库使用指南
  • 本地部署 LangManus