《LeetCode 热题 HOT 100》——寻找两个正序数组的中位数

news2025/1/15 18:08:01

本期给大家带来的是是《LeetCode 热题 HOT 100》第四题——寻找两个正序数组的中位数的题目讲解!!!()


本文目录

💥题意分析

💥解题思路:

1、直接法  (❌)

2、归并思想 (❌)

①《LeetCode》第88题——合并两个有序数组

3、二分查找(✔️)

整体思想:


题目如下 :👇

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。

算法的时间复杂度应该为 O(log (m+n)) 

示例 1:

输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2

示例 2:

输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

💥题意分析

首先,我们需要注意的一点就是关于本题时间复杂度的要求,本题要求的时间复杂度为  O(log (m+n)) ,这里一定要特别注意!!

其次,我们来对给出的示例给出解释说明,分析题意:

  1. 首先对于示例一,题目给出了两个数组——nums1和nums2,两个数组的的合并之后且排好序之后为 【1 2 3】,又因为是 double型 输出,此时它的中位数就是 【2.00000】;
  2. 其次对于示例二,题目给出了两个数组,数组中的元素分别为【1 2】和【3 4】,此时经过排序,我们得到【1 2 3 4】这样的数组,此时数组的长度为偶数,因此中位数的计算就是【(2 + 3) / 2 = 2.5】


💥解题思路:

1、直接法  (❌)

首先,大家拿道题,第一种想到的可能就是直接对这两个数组进行合并在进行排序处理,排完序之后紧接着根据数据长度的奇偶性,我们来判断中位数的到底是n/2还是n/2+1:

代码如下:


class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
 vector<int>arr;

    for(auto& e :nums1) {arr.push_back(e);}
    for(auto& e :nums2) {arr.push_back(e);}
    sort(arr.begin(),arr.end());

    int m=arr.size();
    if( m % 2 == 0){
     return (double)(arr[m/2]+arr[m/2-1]) /2.0;
    } else{
    return arr[m/2];
    }
  }
};

 💨  这种做法可以通过但是却不符合提本题的要求,因为它的时间复杂度就为了 O((m+n)log (m+n)),这样做时间复杂度就取决于排序的代价。因此,这种方法我就此忽略。


2、归并思想 

其实我们在稍加思考,就可以想到一种优化方法。

 💨  我们可以联想到归并排序,这个我想大概应该都学过的。我们不用在进行先合并在sorted,我们可以直接把这两个归并为一个已经排序好的数组。此时这就是双指针的算法,时间复杂度此时为O(m+n)

这个还可以进行优化,我们不需要对全部进行归并操作,因为我们只关心中位数,因此我们可以计算中位数的下标,假设此时中位数的下表为 k,时间复杂度就为O(k),k的取值取决于数组的长度还是,介于(m+n)/2和 (m+n)/2+1。但是此时依然也不能满足我们题意的 log 级别的时间复杂度的要求.
 


《LeetCode》第88题——合并两个有序数组

注意:

  • 如果大家对这个排序还不熟悉的小伙伴,我在这里就连带还给出了以下题目的讲解,帮助大家认识 归并排序 思想 :
  • 《LeetCode》第88题——合并两个有序数组  

题目如下:

给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。

请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。

注意:

  • 最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。

示例 1:

输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。

示例 2:

输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]
解释:需要合并 [1] 和 [] 。
合并结果是 [1] 。

示例 3:

输入:nums1 = [0], m = 0, nums2 = [1], n = 1
输出:[1]
解释:需要合并的数组是 [] 和 [1] 。
合并结果是 [1] 。
注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。

💥题意分析:

  1. 首先,根据题意,我们可以知道给出了的数据都是已经排好序的了,因此,这就减少了我们的一步操作;
  2. 紧接着我们可以发现最终是由nums1返回,因此它的空间都足够大

💥解题思路:

1、直接法

  • 最容易想到的办法无疑是把数组 nums2​ 的数据元素全部放入到数组 nums1 的尾部,因此题意告诉我们 nums1 的空间大小为【m+n】,然后直接对整个数组进行排序即可。

代码如下:

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
           //此时我们选nums1作为 填充数组,把nums2中元素全部插入到nums1中
            for(int i=0; i<n; ++i) 
            {
                nums1[m+i]=nums2[i];
            }
            //此时,全部的元素都在nums1数组中了,最后排序输出即可
            sort(nums1.begin(),nums1.end());
 }
};

性能分析:

  • 时间复杂度:跟我们上述暴力解《寻找两个正序数组的中位数》一样,此时 时间复杂度为O((m+n)log(m+n))
  • 空间复杂度因为初始时nums1 的空间长度大小已经为 (m+n)了。因此无需在开辟额外的空间对其进行存放操作,因此 空间复杂度为O(1)

2、归并排序思想

  • 如果我们仔细观察这到题很明显就是需要利用 二路归并排序 的思想来做。先确定排序后数组的长度,紧接着比较两数组最后的元素的大小,大的放入新数组的最后一位。(因为本题 nums1 数组的长度是 (m+n),所以我们直接覆盖到 nums1 数组之后即可)

图解:

  • 开始时如下图所示:

  •  第一步,比较 3和6 的大小,此时 6比3 大,因此,把6插入到末尾,同时 l2和tail 两个指针同时往前移动一个位置

  •  第二步:此时在比较 3和5 的大小,发现5比3大,因此同上述操作一样,把 5 插入到 tail指向的位置,同时两个指针在往前移动一位

 

  • 第三步:此时比较 3和2 的大小,发现此时 3比2 大,因此,把3插入到 tail指向的位置,同时  tail和 l1 在往前移动一位

  •  第四步:紧接着再次比较 2和2 的大小,发现此时一样大,随便取 l1和l2 位置对应的元素插入到 tail处即可,我们这里是把 l2位置处的 插入到 tail 处。同时移动 l2和tail 

 

  • 第五步:此时 nums2的数据 已经处理完毕了,nums1 中还有数据,只需把 nums1 剩余的元素 放入 tail 所指的位置即可,最后完成排序。

代码如下:

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        int l1 = m - 1;
        int l2 = n - 1;
        int tail = m + n - 1;
        while (l1 >= 0 && l2 >= 0)
        {
            if (nums1[l1] > nums2[l2])
            {
                nums1[tail--] = nums1[l1--];
            }
            else
            {
                nums1[tail--] = nums2[l2--];
            }
        }
        while (l1 >= 0)
        {
            nums1[tail--] = nums1[l1--];
        }
        while (l2 >= 0)
        {
            nums1[tail--] = nums2[l2--];
        }
    }
};

性能分析:

  • 时间复杂度:指针移动单调递减,最多移动 m+n 次,因此 时间复杂度为 O(m+n)
  • 空间复杂度:直接对数组 nums1 原地修改,不需要额外空间,因此 空间复杂度为O(1)

温馨小提示:

我们除了从后往前操作之外,还有从前往后操作。这个任务就交给大家了,原理跟上面讲到的一样的!!!


3、二分查找(✔️)

最后,其实我们可以想到,要想实现 log 级别的时间复杂度,最容易想到的就是采用二分查找的思想。
但是二分查找,我们之前学过的都是对一个数组进行二分查找,本题我们这里有两个数组,因此此题的难度我们可以看到标的是 困难。但是也不要害怕,接下来我带大家分析一波
 

整体思想:

  • 更有效的方法是使用二叉搜索,我们可以在两个数组中找到分区点,使得分区点左侧的元素小于右侧的元素。然后,可以通过取左分区的最大值和右分区的最小值来找到中位数!!!

图解:

 

  •  分区点不满足的示例:

  • 极端情况1:

  •  极端情况2:

 

 

具体步骤:

  1. 首先,记录下两个数组的长度大小,以备后序的计算中位数做准备;
  2. 为了防止分区点的在第二个数组的两侧都有元素,导致出现数组越界的现象。在此实现中,我们首先检查第一个数组的长度是否大于第二个数组的长度。如果是,我们交换数组以确保第一个数组始终是较短的那个数组;
  3. 然后,我们使用二叉搜索来查找两个数组的分区点,我们计算分区点。使得两个数组中分区左侧的元素数小于分区右侧的元素数;
  4. 找到较短数组的分区点partition1,利用公式找到较长数组的分区点partition2;
  5. 然后我们检查分区点是否在数组的边界,如果没有,我们检查分区点处元素是否形成有效的中位数; 如果没有,我们可以相应的调整分区点;
  6. 如果分区点位于数组的边界或者分区点处的元素形成有效的中位数,我们可以计算两个数组的中位数;
  7. 然后,我们计算两个数组中分区左侧的最大元素和两个数组中分区右侧的最小元素,如果较短数组中分区左侧的最大元素小于或等于较长数组中分区右侧的最小元素 ,并且 较短数组中分区右侧的最小元素大于等于较长数组中分区左侧的最大元素 ,然后表明我们找到了中位数;
  8. 如果合并后数组的长度是偶数,我们取分区左侧最大元素和分区右侧最小元素的平均值;如果合并后数组的长度是奇数,我们取分区左侧最大元素作为作为中位数

代码如下:

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
    //记录两个数组的长度大小
    int m = nums1.size();
    int n = nums2.size();

    //确保第一个数组始终是较短数组
    if (m > n) 
    {
        return findMedianSortedArrays(nums2, nums1);
    }

    //在较短数组的区间 【0,m】之间里查找出合适的分区点
    //使得较短数组满足我们需要的条件 
    int left = 0;
    int right = m;

    while (left <= right) {
        //找到较短数组的分区点partition1
        int partition1 = (left + right) / 2;
        //利用公式找到较长数组的分区点partition2
        int partition2 = (m + n + 1) / 2 - partition1;

        //然后我们检查分区点是否在数组的边界,如果没有,我们检查分区点处元素是否形成有效的中位数
        //如果没有,我们可以相应的调整分区点

        //较短数组找到分区点后,因为数组是已经排好序的
        //当 partition1=0 时,说明较小数组分割线左边没有值,为了不影响
        //nums1[partition1] <= nums2[partition2]和 Math.max(maxLeft1, maxLeft2)的判断
        //所以将它设置为int的最小值,即INT_MIN
        int maxLeft1 = (partition1 == 0) ? INT_MIN : nums1[partition1 - 1];

        //较短数组找到分区点后,因为数组是已经排好序的
        //partition1 等于较小数组的长度时,说明较小数组分割线右边没有值,为了不影响
        // nums2[partition2] <= nums1[partition1] 和 Math.min(minRight1, minRight2)的判断,
        //所以将它设置为int的最大值,即INT_MAX
        int minRight1 = (partition1 == m) ? INT_MAX : nums1[partition1];

        //较长数组找到分区点后,因为数组是已经排好序的
        //当partition2等于0时,说明较长数组分割线左边没有值,为了不影响
        // nums2[partition2] <= nums1[partition1] 和 Math.max(maxLeft1, maxLeft2)的判断,
        //所以将它设置为int的最小值,即INT_MIN
        int maxLeft2 = (partition2 == 0) ? INT_MIN : nums2[partition2 - 1];

        //较长数组找到分区点后,因为数组是已经排好序的
        //当partition2 == n,说明较长数组分割线右边没有值,为了不影响
        //nums1[partition1] <= nums2[partition2] 和 Math.min(minRight1, minRight2)的判断,
        //所以将它设置为int的最大值,即INT_MAX
        int minRight2 = (partition2 == n) ? INT_MAX : nums2[partition2];
        
        //然后,我们计算两个数组中分区左侧的最大元素和两个数组中分区右侧的最小元素
        //如果较短数组中分区左侧的最大元素小于或等于较长数组中分区右侧的最小元素 并且
        //较短数组中分区右侧的最小元素大于等于较长数组中分区左侧的最大元素 ,然后表明我们找到了中位数
        if (maxLeft1 <= minRight2 && maxLeft2 <= minRight1) {
            //如果合并后数组的长度是偶数,我们取分区左侧最大元素和分区右侧最小元素的平均值
            if ((m + n) % 2 == 0)            
            {
                return (max(maxLeft1, maxLeft2) + min(minRight1, minRight2)) / 2.0;
            }
            //如果合并后数组的长度是奇数,我们取分区左侧最大元素作为作为中位数
            else 
            {
                return max(maxLeft1, maxLeft2);
            }
        } 
        //此处用了maxleft1 < minRight2的取反,当较小数组中分区点的左边的值大于较长数组中分区点的右边的值
        //表面右指针应该左移
        else if (maxLeft1 > minRight2)         
        {
            right = partition1 - 1;
        } 
        //满足条件,左指针继续右移到 partition1 + 1位置处
        else 
        {
            left = partition1 + 1;
        }
    }
    return 0.0;
 }
};

性能分析:

  •  时间复杂度:O(logmin(m,n))),其中 m 和 n 分别是数组 nums1 和 nums2的长度。查找的区间是 [0,m],而该区间的长度在每次循环之后都会减少为原来的一半。所以,只需要执行 logm 次循环。由于每次循环中的操作次数是常数,所以时间复杂度为 O(logm)。由于我们可能需要交换 nums1 和 nums 2 使得 m≤n,因此时间复杂度是 O(logmin(m,n)))
  • 空间复杂度:因为不需要借助额外的空间,因此 空间复杂度为O(1)

到此,本题便讲解结束了!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/415832.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023年证券、基金、银行从业资格证考试计划

一、证券从业&#xff1a; 考试时间&#xff1a;统一测试拟于6月3日-4日举办1次&#xff0c;在全国41个城市举办。 报名网站&#xff1a;考试报名-中国证券业协会 (sac.net.cn) 目标&#xff1a;一般从业资格考试(入门资格考试) 考试人群&#xff1a;即将进入证券业从业的人…

Linux命令·ping

Linux系统的ping命令是常用的网络命令&#xff0c;它通常用来测试与目标主机的连通性&#xff0c;我们经常会说“ping一下某机器&#xff0c;看是不是开着”、不能打开网页时会说“你先ping网关地址192.168.1.1试试”。它通过发送ICMP ECHO_REQUEST数据包到网络主机&#xff08…

【ROS2指南-5】理解ROS2服务

目标&#xff1a;使用命令行工具了解 ROS 2 中的服务。 教程级别&#xff1a;初学者 时间&#xff1a; 10分钟 内容 背景 先决条件 任务 1 设置 2 ros2服务列表 3 ros2服务类型 4 ros2 服务查找 5 ros2界面展示 6 ros2 服务调用 概括 下一步 相关内容 背景 服务是 …

Ubuntu18.04安装linux-lab

Ubuntu18.04安装linux-lab 文章目录Ubuntu18.04安装linux-labdocker安装linux-lab安装意外事件流处理参考资料本文主要目的是搭建linux内核实验环境 因为工作需要所以学习linux内核&#xff0c;目前主要根据《linux内核完全注释》和《自己动手写操作系统》进行学习&#xff0c…

代码随想录【链表】---->反转链表、两两交换链表中的节点

文章目录206. 反转链表思路双指针实现递归写法24. 两两交换链表中的节点思路代码实现206. 反转链表 题目LeetCode206. 反转链表 思路 翻转链表实际上只需要将每一个节点的指针域指向前一个节点即可&#xff0c;原来第一个节点的指针域指向NULL指针 原头节点是1&#xff0c…

前端的性能对业务数据的影响

性能总论 一切没有 profiling 的性能都是耍流氓。凡是真正有价值的性能优化&#xff0c;必定是从端到端的业务场景建立体系来考虑的。 性能体系的建立可以分成以下几部分&#xff1a; 现状评估和建立指标&#xff1b;技术方案&#xff1b;执行&#xff1b;结果评估和监控。 …

【博学谷学习记录】超强总结,用心分享 | 架构师 Redis学习总结

文章目录1.Redis概述&安装配置安装启动2.Redis的Key的设计规范1、key名设计2、避免bigkey**string字符串类型**hash类型&#xff08;散列表&#xff09;list列表类型set集合类型sortedset有序集合类型bitmap位图 类型geo地理位置类型1.Redis概述&安装配置 官网&#x…

SQL SERVER数据库生成数据字典并且导出方法

SQL SERVER数据库生成数据字典并且导出方法打开SQL SERVER 2014找到你所需要建立数据字典的数据库在代码区输入如下SQL语句点击运行&#xff0c;导出或者带标题复制出来打开SQL SERVER 2014找到你所需要建立数据字典的数据库 右键→点击 新建查询 在代码区输入如下SQL语句 S…

椭圆型偏微分方程和格林函数

一、本文先简单地介绍一下Green 函数&#xff0c; 第一部分内容来自于文献 [0]BI-GreenNet: Learning Green’s Functions by Boundary Integral Network [1] Evans, L.C.: Partial Differential Equations. American Mathematical Society, Providence, R.I. (2010) [2]Learn…

Redis数据库的安装和命令使用以及python的调用

Redis 简介 Redis是完全开源免费的&#xff0c;是一个高性能的key-value数据库。 Redis与其他 key- value 缓存产品有以下三个特点&#xff1a; Redis支持数据的持久化&#xff0c;可将内存中的数据保存在磁盘中&#xff0c;重启时再次加载使用。Redis不仅支持简单的key-val…

故障定级和定责

故障管理的第一步是对故障的理解&#xff0c;只有正确地面对故障&#xff0c;我们才能够找到更合理的处理方式。 这便需要做两个工作&#xff1a;一是跟踪线上故障处理和组织故障复盘&#xff0c;二是制定故障定级定责标准&#xff0c;同时有权对故障做出定级和定责。 所以&a…

网上选课系统——管理员子系统的设计与实现

本系统是C/S&#xff08;客户端/服务器&#xff09;两层结构。采用C#数据库编程语言。服务器部分是SQL Server2000 关系数据库。客户端分为学生选课子系统和管理员管理子系统&#xff0c;它们通过存于后台数据库的数据产生联系。 无论是学生子系统还是管理员子系统&#xff0c…

Follow My Heart Of Apirl. 2023

This article will tell all my story in my daily file to make some deep memories in my heart for Apirl.2023. The line template: The template will be used as the mark of the storys start. 2023.04.10 Commodity n. 货物、商品、日用品 1. Global commodity prices …

《OpenCV3和Qt5计算机视觉应用开发》学习笔记

Qt Creator中使用opencv 在.pro文件中添加 INCLUDEPATH D:\MajorSoftware\opencv\opencv\build\include LIBS D:\MajorSoftware\opencv\opencv\mingw_build\lib\libopencv_*.a一个插件就是一个简单的库&#xff0c;如.dll&#xff0c;可以在运行时加载和使用插件&#xff…

基于模型预测控制(MPC)的微电网调度优化的研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

7针0.96寸OLED的HAL库代码(硬件SPI/全代码/stm32f1/CubeMX配置/包含有正负浮点数/100%一次点亮)

系列文章链接 HC-SR04超声波模块的使用 编码电机以及双电机驱动 4针 0.96OLED的使用 更多有意思的文章点击“我的主页” --------&#x1f610; 更多有意思的视频 -----> B站 想要亿只独角兽 --------&#x1f610; 前言 之前发布了一篇硬件I2C的0.96 OLED驱动代码&#…

全国计算机等级考试——二级JAVA完整大题题库【五十三道】

全国计算机等级考试二级 JAVA 题目内容 编写于2023.04.10 分为40道选择题和3道大题&#xff08;大题是程序填空类型&#xff09; 其中选择题只能进去做一次&#xff0c;一旦退出来则不可再进&#xff08;注意&#xff01;&#xff09;。大题可以重复进入&#xff0c;重复做。…

JavaScript【七】JavaScript中的内置顶层函数

文章目录&#x1f31f;前言&#x1f31f;内置&#x1f31f;顶层&#x1f31f;内置顶层函数&#x1f31f; Number()&#x1f31f; parseInt()&#x1f31f;parseFloat()&#x1f31f;String()&#x1f31f;Boolean()&#x1f31f; isNaN()&#x1f31f;eval()&#x1f31f;Numbe…

(详细)《美国节日》:某月的第几个星期几

目录 一、题目描述&#xff1a; 二、思路&#xff1a; 1、给定 年月日&#xff0c;如何知道这天是星期几&#xff1f; 2、已知这个月的第一天是星期几&#xff0c;如何知道第三个星期一是几号&#xff1f; 3、最后一个星期一 三、思路总结 四、代码 一、题目描述&#xf…

机器学习---集成学习报告

1.原理以及举例 1.1原理 集成学习&#xff08;Ensemble Learning&#xff09;是一种机器学习策略&#xff0c;它通过结合多个基学习器&#xff08;base learners&#xff09;的预测来提高模型的性能。集成学习的目标是创建一个比单个基学习器更准确、更稳定的最终预测模型。这…