【LeetCode】4,寻找两个正序数组中的中位数

news2025/6/26 13:22:02

题目地址
B站那个官方解答视频实在看不懂,我就根据他那个代码和自己的理解写一篇文章

1. 基本思路

在只有一个有序数组的时候,中位数把数组分割成两个部分。中位数的定义:中位数,又称中点数,中值。中位数是按顺序排列的一组数据中居于中间位置的数,即在这组数据中,有一半的数据比他大,有一半的数据比他小,如果数据个数为奇数的时候,则排序后的数据最中间的数是中位数,如果数据个数为偶数的时候,则排序后的数据最中间的两个数的均值称为中位数。
根据中位数的定义,我们要分数组长度为奇数和偶数进行讨论:
(1)首先,将数组一分为二,数组长度为偶数的时候,中位数有两个,其中一个是左边数组的最大值,另一个是右边数组的最小值。

数组长度为奇数的时候,中位数有1个,不妨设中位数分到左边数组。

在两个有序数组的时候,我们仍然可以把两个数组分别按照上面的规则分割为两个部分(注意是将两个数组排成两行然后再对这个整体分割,不是单独分割去分割两个数组中的每一个数组)。
我们使用一条分割线把两个数组分别分割成两部分:
(1)如果两个数组的长度之和为偶数,则让红线左边和右边的元素个数相等;如果两个数组的长度之和为奇数,则让红线左边元素的个数比右边元素的个数多1个;
(2)红线左边的所有元素的值 ≤ \le 红线右边的所有元素的值;
那么中位数就一定只与红线两侧的元素有关,确定这条红线的位置使用二分查找。

优化第1个条件
假设数组1的长度为 m m m,数组2的长度为 n n n
m + n m+n m+n为偶数的时候,左侧数组长度为 m + n 2 \frac{m+n}{2} 2m+n
m + n m+n m+n为奇数的时候,由于我们之前假设中位数被分到左边,则左侧数组长度为 m + n + 1 2 \frac{m+n+1}{2} 2m+n+1(即向上取整)
由于整数除法是向下取整,则可以将当 m + n m+n m+n为偶数的时候,左侧数组长度等效为 m + n + 1 2 \frac{m+n+1}{2} 2m+n+1
因此,左侧数组长度可以合并为 m + n + 1 2 \frac{m+n+1}{2} 2m+n+1
优化第2个条件
为了保持红线左边的所有元素的值 ≤ \le 红线右边的所有元素的值,由于两个数组都是有序数组,在同一个数组内,分割线一定满足左边的所有元素小于等于右边元素。在不同的数组之间,应该保证交叉小于等于关系成立,如下图:

那么只要不符合小于等于关系,我们就需要适当调整分割线的位置。
(1)情况1

虽然这个是数组之和为奇数,左半部分数组的元素个数比右半部分数组的元素个数多1,但是第二个数组(1, 7, 8, 10, 17)分割线左边位置的最大值8>第一个数组(2, 4, 6, 15)分割线右边位置的最小值6就不符合红线左边的所有元素的值 ≤ \le 红线右边的所有元素的值,也就是说中位数右边的数太小了,调整方案:将中位数分割线在数组1的位置右移
(2)情况2:

第一个数组(2, 4, 6, 8, 10, 17)分割线左边位置的最大值8>第二个数组(1, 7, 15, 10)分割线右边位置的最小值7就不符合红线左边的所有元素的值 ≤ \le 红线右边的所有元素的值,也就是说中位数左边的数太大了,调整方案:将中位数分割线在数组1的位置左移

二分查找算法就是在这样尝试找到恰当的分割线的过程当中,不断地缩小搜索区间的范围,直到最终找到符合条件的分割线的位置,又由于我们需要比较分割线两侧元素的大小关系,在返回数组下标的时候,很有可能就出现下面两类极端的情况:
(1)较短的数组在分割线的右边没有元素和较短的数组在分割线的左边没有元素


由于我们需要通过访问“中间数分割线”左右两边的元素,因此应该在较短的数组上确定“中间数分割线”的位置。
(3)第二类情况发生在两个数组长度相等的时候
第一种,第一个数组在分割线的右边没有元素,并且第二个数组在分割线的左边没有元素

第二章,第二个数组在分割线的左边没有元素,并且第二个数组在分割线的右边没有元素


分割线的定义:
分割线在第一个数组右边的第1个元素的下标为i = 分割线在第一个数组左边的元素个数
分割线在第二个数组右边的第1个元素的下标为j = 分割线在第二个数组左边的元素个数

观察到,两数组长度和为奇数的时候,每个数组的分割线左侧的第一个元素的最大值即为中位数,两数组长度和为偶数的时候,每个数组的分割线左侧的第一个元素的最大值和右侧的第一个元素的最小值的均值即为中位数。

2. 代码实现

C语言实现该题,思路全在注释中:

//选出两个整数之间的最小值和最大值
int Min(int a, int b)
{
    if (a > b)
    {
        return b;
    }
    else
    {
        return a;
    }
}
int Max(int a, int b)
{
    if (a < b)
    {
        return b;
    }
    else
    {
        return a;
    }
}
double findMedianSortedArrays(int* nums1, int nums1Size, int* nums2, int nums2Size) {
    //如果有空数组,则直接返回非空数组的中位数
    if(nums1 == NULL || nums1Size == 0)
    {
        //奇数长度返回中间,偶数取中间两个均值
        if(nums2Size % 2 == 0)
        {
            int mid = (nums2Size - 1) >> 1; //返回的是下标中点,不是数量中点
            return (double)((nums2[mid] + nums2[mid + 1]) / 2.0);
        }
        else
        {
            return nums2[nums2Size / 2];
        }
    }
    if(nums2 == NULL || nums2Size == 0)
    {
        //奇数长度返回中间,偶数取中间两个均值
        if(nums1Size % 2 == 0)
        {
            int mid = (nums1Size - 1) >> 1;
            return (double)((nums1[mid] + nums1[mid + 1]) / 2.0);
        }
        else
        {
            return nums1[nums1Size / 2];
        }
    }
    //如果第一个数组的长度大于第二个数组的长度,那就交换以下,让较短的数组成为第一个数组
    if(nums1Size > nums2Size)
    {
        //交换数组指针
        int* temp = nums1;
        nums1 = nums2;
        nums2 = temp;
        //交换数组长度变量
        int tmp = nums1Size;
        nums1Size = nums2Size;
        nums2Size = tmp;
    }
    //数组1和数组2的长度分别用变量m和n表示
    int m = nums1Size;
    int n = nums2Size;
    //分割线左侧元素个数
    int totalLeft = (m + n + 1)>>1; //向右移动1位相当于除2
    //在nums1的区间[0, m]里查找恰当的分割线。
    //分割线在第一个数组左边的最大值nums1[i-1]要小于等于分割线在第二个数组右边的最小值nums2[j]
    //并且,分割线在第二个数组左边的最大值nums2[j-1]要小于等于分割线在第一个数组右边的最小值nums1[i]
    //这个就是我们分析出来的交叉的不等关系
    int left = 0;
    int right = m;
    //以长度最小的数组做循环变量的二分
    while(left < right)
    {
        //分割线在第一个数组的下标
        //+1的原因:
        //比如偶数长度的数组[1, 2, 3, 4]
        //开始的(left + right) / 2为1,其实我们的i应该是分割线右侧,即i为2,我们应该向上取整,向上取整就应该+1后再除2
        //奇数长度因为我们也要将i设置为中位数右侧第一个元素下标的值(对应上面文中的规则)
        int i = (left + right + 1) >> 1;
        //分割线在第二个数组的下标
        //比如两个数组[1, 2]和[3, 4, 5, 6]
        //i=0,则我们没有把第二个数组的开始和结束下标记作left和right
        //只能通过totalLeft间接推出
        //totalLeft=3,totalLeft是两个数组合并后的相对的左侧数组的元素个数
        //totalLeft-i相当于把左侧数组中第一个数组左侧的元素数减掉,只剩下第二个数组左侧的元素数
        //第二个数组左侧元素的个数恰好就是第二个数组的分割线的位置j
        int j = totalLeft - i;
        // 第一个数组中分割线左侧的元素大于第二个数组中分割线右侧的元素
        // 说明分割线在第一个数组上的位置太靠右了,所以分割线位置在i这个位置的左侧(不包括i)
        // 所以下一轮位置,所以下一轮搜索区间是[left, i - 1]
        if(nums1[i-1] > nums2[j])
        {
            right = i - 1;
        }
        //
        else
        // 如果恰好满足条件了,则将第一个数组右侧的元素和第二个数组左侧的元素算作两个新数组继续二分
        {
            left = i;
        }
    }
    //确定二分到最后的数组的分割线
    int i = left;
    int j = totalLeft - i;
    //求出第一个数组的分割线的左侧的最大值和右侧的最小值的变量
    //第二个数组以此类推
    //先初始化为0
    int nums1LeftMax = 0;
    int nums1RightMin = 0;
    int nums2LeftMax = 0;
    int nums2RightMin = 0;
    //如果最后的分割线i为0时,说明第一个数组分割线左侧数组的最大值不存在,则令此时的nums1LeftMax = INT_MIN
    //如果最后的分割线i为m时,说明第一个数组分割线右侧数组的最小值不存在,则令此时的nums1RightMin = INT_MAX
    //同理第二个数组j为0或者n时,以此类推
    if(i == 0)
    {
        nums1LeftMax = INT_MIN;
        nums1RightMin = nums1[i];
    }
    else if(i == m)
    {
        nums1LeftMax = nums1[i-1];
        nums1RightMin = INT_MAX;
    }
    else
    {
        nums1LeftMax = nums1[i-1];
        nums1RightMin = nums1[i];
    }
    if(j == 0)
    {
        nums2LeftMax = INT_MIN;
        nums2RightMin = nums2[j];
    }
    else if(j == n)
    {
        nums2LeftMax = nums2[j-1];
        nums2RightMin = INT_MAX;
    }
    else
    {
        nums2LeftMax = nums2[j-1];
        nums2RightMin = nums2[j];
    }
    //如果是奇数,返回的是分割线左侧的两个数组对应的元素的最大值
    //即分割线左侧要找到的是最大值
    //如果是偶数,返回的是分割线左侧的两个数组对应的元素的最大值和右侧的最小值的均值
    if((n+m)%2 == 1)
    {
        return Max(nums1LeftMax, nums2LeftMax);
    }
    else
    {
        return (double)((Max(nums1LeftMax, nums2LeftMax) + Min(nums1RightMin, nums2RightMin)) / 2.0);
    }
}

最后也是顺利通过:

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

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

相关文章

全志摄像头屏幕预览、录制(H264)

一、录像 使用dvr_test录制视频 运行dvr_test demo出现space not enought问题&#xff0c;修改/etc/dvrconfig.ini文件下对应的camera节点下cur_filedir属性无效 修改以下内容解决; 录制时出现摄像头画面异常&#xff0c;如下 摄像头型号与打印信息匹配&#xff1a; 但是出现画…

整蛊软件/插件使用方法与配置步骤~

今天出一期整蛊软件的使用方法与配置步骤 很多人在使用整蛊软件的时候 想自己添加更多的玩法内容 但是还不知道如何去配置 这期给大家出一下图文教程步骤 基本上也是软件的功能介绍使用方式~ 案例可扫码查看 第一步&#xff1a;打开软件输入卡密登录&#xff1a; 卡密费用&…

最好用的邮箱管理软件推荐,邮箱管理软件哪个好?(干货篇)

在快节奏的工作与生活中&#xff0c;有效管理电子邮件成为提升个人与团队效率的关键。 面对海量信息流&#xff0c;一款好的邮箱管理软件不仅能够帮助我们高效地整理收件箱&#xff0c;还能确保重要邮件不会错过&#xff0c;同时提升通讯的便捷性和安全性。 本文将为您推荐几款…

AUTOSAR平台中的信息安全标准模块

面向MCU端的AUTOSAR CP平台加密组件——Crypto ECU中所有的软件单元都遭受到信息安全攻击的可能。AUTOSAR为保障ECU信息和数据安全&#xff0c;定义了CRYPTO 组件,包含 SecOC、KeyM、IdsM、Csm、CryIf 和Crypto Driver 等标准模块。CRYPTO组件提供各种加解密算法以及密钥管理功…

另辟蹊径的终端防病毒

在数字时代的浪潮中&#xff0c;网络安全问题愈发凸显&#xff0c;防病毒成为了保护信息安全的重要一环。而白名单作为一种有效的安全策略&#xff0c;在防病毒方面发挥着不可或缺的作用。 首先&#xff0c;我们需要明确白名单的概念。白名单是一种管理和安全实践&#xff0c;用…

掌握这招,串口通信高频收发32W数据,0丢包!

我做过挺多串口通讯类的产品&#xff0c;有用485通讯的pdu&#xff0c;有wifi/4G/蓝牙通讯类的网关... 做串口的应用&#xff0c;把串口外设调通只是第一步&#xff0c;串口只是数据传输的媒介&#xff0c;在此基础上&#xff0c;两个设备/器件要进行通信&#xff0c;传递更有意…

软件使用教程

昨天分享了一款专业软件&#xff0c;但是大家不知道怎么pj&#xff0c;好多小伙伴被文件夹下面的说明带跑偏了&#xff0c;所以今天特地发个文说一下科学使用的过程&#xff0c;需要软件的可以看昨天的文章&#xff01; 话不多说那好小板凳&#xff0c;教程开始了 1、第一步&…

18. 四数之和 - 力扣

1. 题目 给你一个由 n 个整数组成的数组 nums &#xff0c;和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] &#xff08;若两个四元组元素一一对应&#xff0c;则认为两个四元组重复&#xff09;&#xff1a; 0 …

空间搜索geohash概述;redis的geo命令

概述 通常在一些2C业务场景中会根据用户的位置来搜索一些内容。通常提供位置搜索的都是直接通过redis/mongodb/es等中间件实现的。 但是这些中间件又是怎么实现位置搜索的呢&#xff1b; 查了一番资料&#xff0c;发现背后一个公共的算法Geohash。 搜索的时候可以根据距离对…

认识Redis 主从同步、事务和Memcached的区别

08- 什么是 Redis 主从同步&#xff1f; Redis 的主从同步(replication)机制&#xff0c;允许 Slave 从 Master 那里&#xff0c;通过网络传输拷贝到完整的数据备份&#xff0c;从而达到主从机制。 主数据库可以进行读写操作&#xff0c;当发生写操作的时候自动将数据同步到从…

Spring Boot:Java 应用开发高效之道

Spring Boot 是一种革命性的框架&#xff0c;旨在简化 Java 应用的创建和部署过程。通过自动化配置和简化项目搭建流程&#xff0c;Spring Boot 大大加速了开发周期&#xff0c;让 Java 应用开发变得更加高效和便捷。 核心优势&#xff1a; 快速启动和简化配置&#xff1a;Spr…

【C++】STL中list的使用

前言&#xff1a;在前面学习的 过程中我们学习了STL中的string,vector&#xff0c;今天我们来进一步的学习STL中的list的使用方法。 &#x1f496; 博主CSDN主页:卫卫卫的个人主页 &#x1f49e; &#x1f449; 专栏分类:高质量&#xff23;学习 &#x1f448; &#x1f4af;代…

设置sqlserver management的字体大小

在用sqlserver management的时候&#xff0c;总感觉怪怪的&#xff0c;然后发现是字体太小的原因。 1&#xff09;设置一下字体&#xff0c;工具--选项&#xff1a; 2&#xff09;环境--字体和颜色--显示其设置&#xff08;环境&#xff09; 3&#xff09;选择微软雅黑&#xf…

【Linux】从零开始配置新的服务器的机器学习环境

终端远程登录 ssh -p [端口号] [服务器用户名][服务器IP]或者 ssh [用户名][主机地址]第二种的前提是在.ssh\config中配置了host 安装文本编辑器vim 主要用于后续的文本编辑&#xff0c;个人比较习惯用vim&#xff0c;根据自己喜好选择 更新apt sudo apt update安装文本编辑…

额定值高于 1 kW 的电机驱动应用使用 GaN 逆变器 IC

GaN 技术的三个重要的参数是更高的带隙、临界场和电子迁移率。当这些参数结合起来时&#xff0c;由于 GaN 晶体的临界场高 10 倍&#xff0c;因此与硅 MOSFET 相比&#xff0c;电端子之间的距离可以近 10 倍。这导致了 GaN 和硅之间的明显区别&#xff1a;中压 GaN 器件可以基于…

【C++】类的默认成员函数

类的默认成员函数 类的六个默认成员函数构造函数构造函数的概念构造函数的特性 析构函数析构函数的概念析构函数的特性 构造函数与析构函数的调用顺序拷贝构造拷贝构造的概念拷贝构造的特性赋值运算符重载运算符重载赋值运算符重载前置与后置重载输入输出流重载 const修饰成员实…

STM32开发过程中碰到的问题总结 - 2

文章目录 前言1、Hex文件和Bin文件的区别2. STM32的boot0 在0和1有什么区别3. keil下的translate和builder有什么区别4. 为什么RTOS中区分中断内调用和中断外调用5. STM32中中断内运行的代码应该注意哪些事项6. STM32的定时器中断中 使用printf没有任何输出7. 将makefile编译的…

浅谈C++基本框架内涵及其学习路线

目录 一.C的内涵本质 1. 面向对象编程&#xff08;OOP&#xff09; 2. 低级控制 3. 模板编程 4. 标准库&#xff08;STL&#xff09; 5. 多范式支持 二.学习路线 1. 基础阶段 C基础语法 函数 数组和指针 2. 面向对象编程 类和对象 继承和多态 运算符重载 3. 高级…

【elementui源码解析】如何实现自动渲染md文档-第二篇

目录 1.概要 2.引用文件 1&#xff09;components.json 2&#xff09;json-template/string 3&#xff09;os.EOL 3.变量定义 4.模版填充 5.MAIN_TEMPLATE填充 6.src下的index.js文件 1&#xff09;install 2&#xff09;export 7.总结 1.概要 今天看第二个命令no…

Blender骨骼创建

骨骼系统 建立 使用Shift A添加骨骼或在添加|骨架中添加一段骨骼 骨骼的三种模式 -物体模式&#xff1a;做动画&#xff0c;摆人物pose时在该模式 -编辑模式&#xff1a;进行骨骼搭建&#xff08;选择一段骨骼&#xff0c;然后按E挤出一段骨骼并进行调整&#xff09; -姿…