首页 > 其他分享 >LeetCode题练习与总结:翻转对--493

LeetCode题练习与总结:翻转对--493

时间:2024-12-24 23:32:26浏览次数:6  
标签:归并 nums -- int 数组 left 排序 LeetCode 493

一、题目描述

给定一个数组 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. 对数组进行归并排序。
  2. 在归并的过程中,对于左半边的每个元素,我们需要在右半边找到第一个大于等于2倍该元素的数。
  3. 找到这样的数之后,那么从右半边第一个大于等于2倍该元素的数到右半边末尾的元素都与该左半边的元素构成重要翻转对。
  4. 统计这些重要翻转对的数量,并加上左右半边分别统计的重要翻转对的数量。

三、具体代码

class Solution {
    public int reversePairs(int[] nums) {
        // 辅助数组,用于归并排序
        int[] temp = new int[nums.length];
        return mergeSort(nums, 0, nums.length - 1, temp);
    }

    private int mergeSort(int[] nums, int left, int right, int[] temp) {
        if (left >= right) {
            return 0;
        }
        int mid = left + (right - left) / 2;
        int count = mergeSort(nums, left, mid, temp) + mergeSort(nums, mid + 1, right, temp);
        // 统计重要翻转对的数量
        count += merge(nums, left, mid, right, temp);
        return count;
    }

    private int merge(int[] nums, int left, int mid, int right, int[] temp) {
        int count = 0;
        int j = mid + 1;
        // 统计重要翻转对
        for (int i = left; i <= mid; i++) {
            while (j <= right && (long)nums[i] > 2L * nums[j]) {
                j++;
            }
            count += j - (mid + 1);
        }
        // 归并排序的合并步骤
        System.arraycopy(nums, left, temp, left, right - left + 1);
        int i = left, k = left;
        j = mid + 1;
        while (i <= mid && j <= right) {
            if (temp[i] <= temp[j]) {
                nums[k++] = temp[i++];
            } else {
                nums[k++] = temp[j++];
            }
        }
        while (i <= mid) {
            nums[k++] = temp[i++];
        }
        while (j <= right) {
            nums[k++] = temp[j++];
        }
        return count;
    }
}

在这段代码中,mergeSort 函数用于递归地对数组进行归并排序,并在归并过程中调用 merge 函数来统计重要翻转对的数量。merge 函数除了执行归并操作外,还负责计算当前区间内的翻转对数量。这里使用了一个小技巧,即通过计算右半边第一个大于等于2倍左半边元素的索引来计算翻转对的数量。注意,在比较时使用了 long 类型来避免整数溢出。

四、时间复杂度和空间复杂度

1. 时间复杂度
  • 归并排序的过程将数组分成两半,每次递归调用都会处理数组的一半大小,因此递归的深度是 O(logN),其中 N 是数组的长度。
  • 在每一层递归中,我们需要对两个子数组进行合并,合并过程中需要遍历两个子数组的所有元素,这一步的时间复杂度是 O(N)。
  • 在合并过程中,我们还需要统计重要翻转对的数量。对于左子数组的每个元素,我们可能需要遍历右子数组来找到第一个大于等于2倍该元素的数,这一步的时间复杂度在最坏情况下是 O(N)。
  • 综合上述步骤,每层递归的时间复杂度是 O(N) + O(N) = O(N),递归深度是 O(logN),因此总的时间复杂度是 O(NlogN)。
2. 空间复杂度
  • 归并排序需要一个与原数组相同大小的辅助数组来存储合并过程中的临时结果,因此空间复杂度是 O(N)。
  • 递归调用栈的深度是 O(logN),因为每次递归都是处理数组的一半大小。
  • 综合上述步骤,总的空间复杂度是 O(N) + O(logN),即 O(N),因为 O(logN) 相对于 O(N) 可以忽略不计。

五、总结知识点

  • 归并排序(Merge Sort)

    • 归并排序是一种分治策略的排序算法,它将数组分成两半,分别对两半进行排序,然后将排序好的两半合并。
    • 归并排序的时间复杂度通常是 O(NlogN),空间复杂度是 O(N)。
  • 递归(Recursion)

    • 递归是一种编程技巧,函数调用自身来解决问题。
    • 在归并排序中,递归用于将问题分解成更小的子问题。
  • 辅助数组

    • 辅助数组用于在合并过程中临时存储数组元素,避免覆盖原数组中的数据。
  • System.arraycopy 方法

    • 这是一个Java标准库中的方法,用于高效地复制数组的一部分到另一个位置。
  • 整数溢出(Integer Overflow)

    • 在比较两个整数相乘时,如果直接使用 int 类型可能会导致溢出。
    • 使用 long 类型来避免在比较 (long)nums[i] > 2L * nums[j] 时发生整数溢出。
  • 二分查找(Binary Search)

    • 在统计重要翻转对的过程中,实际上是通过遍历左子数组,并在右子数组中找到一个合适的位置,这个过程类似于二分查找,但这里是线性查找。
  • 计数逻辑

    • 对于左子数组的每个元素,通过线性查找确定其在右子数组中第一个大于等于2倍该元素的索引,然后通过计算索引差来统计翻转对的数量。
  • 合并过程

    • 合并过程是将两个已排序的子数组合并成一个有序的数组,这是归并排序中的关键步骤。
  • 算法设计

    • 代码展示了如何在排序算法中嵌入额外的逻辑(统计翻转对)而不影响排序本身。
  • 边界条件处理

    • 递归的终止条件是 if (left >= right),这确保了递归能够正确地结束。

以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。

标签:归并,nums,--,int,数组,left,排序,LeetCode,493
From: https://blog.csdn.net/weixin_62860386/article/details/144679652

相关文章

  • C++ 构造函数最佳实践
    @目录1.构造函数应该做什么1.1初始化成员变量1.2分配资源1.3遵循RAII原则1.4处理异常情况2.构造函数不应该做什么2.1避免做大量的工作2.2不要在构造函数中调用虚函数2.3避免在构造函数中执行复杂的初始化逻辑2.4避免调用可能抛出异常的代码3.构造函数的其他最佳实践3......
  • LeetCode题练习与总结:构造矩形--492
    一、题目描述作为一位web开发者,懂得怎样去规划一个页面的尺寸是很重要的。所以,现给定一个具体的矩形页面面积,你的任务是设计一个长度为L和宽度为W且满足以下要求的矩形的页面。要求:你设计的矩形页面必须等于给定的目标面积。宽度 W 不应大于长度 L ,换言之,要求 L>......
  • GNU Make中CPPFLAGS和CXXFLAGS之间的区别
    GNUMake是一个流行的构建工具,用于编译和链接源代码。在GNUMake中,CPPFLAGS和CXXFLAGS都是用于指定编译器选项的变量。它们之间的主要区别在于它们分别适用于C和C++编译器。1、CPPFLAGS是预处理器标志(CPreProcessorFlags)的缩写,它们用于指定C预处理器(cpp)的选项。预......
  • JAVA期末通讯录
    写了挺久的,来CSDN记录一下应用软件:mysql,idea--------------------------------------------------------第一步:连接数据库直接去哔哩哔哩上面找mysql下载,下载完了之后我自身没有配什么环境,直接找的mysql怎么跟idea连接视频先用idea把mysql连接了之后再去看的mysql......
  • Linux 下 alsa 库录音并保存为 WAV 格式
    麦克风列表:[jn@jnbuild]$arecord-l****ListofCAPTUREHardwareDevices****card0:AudioPCI[EnsoniqAudioPCI],device0:ES1371/1[ES1371DAC2/ADC]Subdevices:1/1Subdevice#0:subdevice#0card1:Camera[2KUSBCamera],device0:USBAudio[USBA......
  • A5-1密码算法C语言实现
    #include<iostream>usingnamespacestd;boolx1[19]={0};//用于LFSR_1的向量boolx2[22]={0};//用于LFSR_2的向量boolx3[23]={0};//用于LFSR_3的向量boolkey[......
  • docker 构建最小镜像 - 2MB 不到
    @目录1.编译code2.编写Dockerfile3.构建镜像4.运行起来5.总结1.编译coderoot@jn:/home/jn/Desktop/Docker#cathello.gopackagemainimport("fmt""time")funcmain(){for{fmt.Println("hello~")......
  • Java多态--上转型对象
    Java多态概念实现方式上转型对象概念多态:面向对象的三大特性之一多态一句话概括就是,一个类下面的不同子类的实例,对同一个参数输入,得到不同的输出举例:动物类下的小猫、小狗,两只动物分别拍一下,小猫输出“喵喵喵”,小狗输出“汪汪汪”。实现方式多态的方式:类的继承,方......
  • 线程池前世今生及源码实现
    @目录⭐前序一、是什么(what)@功能构成二、为什么(why)三、何处(where)四、何时(when)五、谁(who)六、怎么样(how)@实践和性能调优策略@线程池源码(Code)[待补充]⭐前序本文讲什么:线程池的概念、工作原理、优势、实际应用中的使用场景和注意事项,以及一些最佳实践和性能调优策略......
  • Linux | scp指令基于WSL在Windows/Ubuntu系统间传输文件
    .背景在Windows系统里,使用WSL连接远程Linux(Ubuntu)服务器是如今一个很常见的操作流程(有利于WFH哈哈)。在使用远程机器的时候,通常需要将本地的文件上传、或将远程的文件下载。问题:如何优雅地将本地文件上传、或将远程的文件下载?.解决方案在网上搜索一番、同时问了GPT,找......