九章算法

2023-11-04

两个排序的数组A和B分别含有m和n个数,找到两个排序数组的中位数,要求时间复杂度应为O(log (m+n))。

在线评测地址:LintCode 领扣

说明

中位数的定义:

  • 这里的中位数等同于数学定义里的中位数
  • 中位数是排序后数组的中间值。

如果有数组中有n个数且n是奇数,则中位数为A[(n-1)/2]

  • A[(n−1)/2]。

如果有数组中有n个数且n是偶数,则中位数为 (A[n / 2] + A[n / 2 + 1]) / 2

  • (A[n/2]+A[n/2+1])/2.
  • 比如:数组A=[1,2,3]的中位数是2,数组A=[1,19]的中位数是10。

样例1

输入:
A = [1,2,3,4,5,6]
B = [2,3,4,5]
输出: 3.5

样例2

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

算法:归并

解题思路

  • 最简单的思路是将两个数组合并,然后返回新数组的中位数。九章算法班中讲过,两个有序数组的合并是经典归并排序算法的一步,我们可以新开一个数组,保存合并后的结果。但是我们这样会做额外的工作,因为我们不必保存整个新数组,只需要知道新数组的中位数即可。
  • 因此,更简便的方法是,使用双指针分别对两个数组遍历,比较两个指针下的元素大小,每次移动更小的指针,通过移动次数确定中位数的位置。

算法流程

  • 令指针p1p2分别指向两个数组,初始指向位置0。我们需要遍历(m + n)/2 + 1次,每次比较两个位置的元素,在第k次比较时,较小的那个值就是两个数组中第k + 1小的数。如果一个指针已经走到了数组末尾,那么移动另一个指针,否则每次将指向较小数的指针后移,直到遍历到中位数。
  • 为了将奇偶情况合并,在代码中用了leftright保存中间值,如果是奇数直接返回right,如果是偶数就返回(left + right) / 2

复杂度分析

  • 复杂度分析:

时间复杂度:O(m+n),mn分别是两个数组的长度。双指针遍历两个数组,指针移动次数是0(m+n)级。

  • 空间复杂度:O(1)。
public class Solution {
    /*
     * @param A: An integer array
     * @param B: An integer array
     * @return: a double whose format is *.5 or *.0
     */
    public double findMedianSortedArrays(int[] A, int[] B) {
        int m = A.length, n = B.length;
        int p1 = 0, p2 = 0;
        int left = -1, right = -1;
        for (int i = 0; i < (m + n) / 2 + 1; i ++){
            left = right;
            // p2 右移
            if (p1 >= A.length || p2 < B.length && A[p1] > B[p2]){
                right = B[p2++];
            }
            // p1 右移
            else {
                right = A[p1++];
            }
        }
        return (m + n) % 2 == 1 ? right : (left + right) / 2.0;
    }
}

算法:二分

解题思路

  • 题目要求时间复杂度为O(log(m+n)),不难想到二分法。双指针方法中,我们一个一个的排除不可能的元素。如果充分利用数组的有序性,就能一半一半的排除。具体来说,假设我们要找第k小数,通过二分,可以每次循环排除掉k/2个数。

算法流程

  • 建立辅助函数getKth,参数有数组AA的起始指针start1和终止指针end1, 相对应的有Bstart2end2,以及整数k,目标是找到A[start1:end1]B[start2:end2]中第k小的元素。
  • 在主程序中,看m + n的奇偶性,并调用getKth函数。如果是奇数,返回数组AB的第(m + n) // 2 + 1小元素;如果是偶数,返回数组AB的第(m + n) // 2小和第(m + n) // 2 + 1小元素的均值。
  • getKth(nums1, start1, end1, nums2, start2, end2, k)函数的实现方法: 如果有数组在[start:end]范围内为空,说明该数组已经排除完毕,第k小的元素一定存在于另一数组中,计算好索引位置返回即可。 如果k为1,说明已经找到第k小的数,那就是A[start1]B[start2]中的较小值,直接返回即可。 定义指针ij,分别指向AB,使得A[start1:i]B[start2:j]的长度分别为k // 2,通过比较A[i]B[j]的大小,我们就可以确定排除哪段元素。 如果A[i] > B[j],说明B[start2:j]不可能为第k小元素。我们对A[start1:end1]B[j + 1:end2]继续送入getKth进行递归,k应该更新为k - (j - start2 + 1)。 * 反之,说明A[start1:i]不可能为第k小元素。我们对A[i + 1:end1]B[start2:end2]继续送入getKth进行递归,k应该更新为k - (i - start1 + 1)

复杂度

  • 时间复杂度:O(log(m+n))。mn分别是两个数组的长度。二分法的复杂度为O(log(m+n))。
  • 空间复杂度:O(1)。
 public class Solution {
    /*
     * @param A: An integer array
     * @param B: An integer array
     * @return: a double whose format is *.5 or *.0
     */
    public double findMedianSortedArrays(int[] A, int[] B) {
       int m = A.length, n = B.length;
        // 如果是奇数
        if ((m + n) % 2 == 1) {
            return getKth(A, 0, A.length - 1, B, 0, B.length - 1, (m + n) / 2  + 1);
        }
        // 如果是偶数    
        int left = getKth(A, 0, A.length - 1, B, 0, B.length - 1, (m + n) / 2);
        int right = getKth(A, 0, A.length - 1, B, 0, B.length - 1, (m + n) / 2 + 1);
        return (left + right) / 2.0;  
    }
    
    private int getKth(int[] A, int start1, int end1, int[] B, int start2, int end2, int k) {
        int len1 = end1 - start1 + 1;
        int len2 = end2 - start2 + 1;
        // 让 len1 的长度小于 len2,这样就能保证如果有数组空了,一定是 len1 
        if (len1 > len2) {
            return getKth(B, start2, end2, A, start1, end1, k);
        }
        // A数组排除完毕
        if (len1 == 0) {
            return B[start2 + k - 1];
        } 
        // 已经找到第k小的数
        if (k == 1) {
            return Math.min(A[start1], B[start2]);
        }
        // 开始二分
        int i = start1 + Math.min(len1, k / 2) - 1;
        int j = start2 + Math.min(len2, k / 2) - 1;

        if (A[i] > B[j]) {
            return getKth(A, start1, end1, B, j + 1, end2, k - (j - start2 + 1));
        }
        else {
            return getKth(A, i + 1, end1, B, start2, end2, k - (i - start1 + 1));
        }
    }
}

快速选择等更多解法见:九章算法

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

九章算法 的相关文章

随机推荐

  • 如何用Python进行股票预测,数据分析带你从小白开始

    在开始这个话题之前请先记住一句友情提醒 股市有风险 投资需谨慎 我们写这个文章并不是鼓励大家去入市 小编本人也不买股票 我们只是在探索Python在股票分析和预测上面能发挥什么样的作用 对于和数据打交道的数据科学家来说 预测证券市场走势远比
  • C语言常见错误分析

    C语言常见错误分析 错误分类 语法错 逻辑错 运行错 0 忘记定义变量 main x 3 y 6 printf d n x y 1 C语言的变量一定要先定义才能使用 2 输入输出的数据的类型与所用格式说明符不一致 int a 3 float
  • PRML_频率与贝叶斯(一)

    我们从数据中能得到以下信息 总体信息 总体所属分布或者所属的分布族带来的信息 样本信息 从总体中抽样得来的样本给我们提供的信息 以上两种信息进行的统计推断称为经典统计学 它的观点是把样本看成来自具有一定概率分布的总体 先验信息 在抽样之前
  • Echarts.js(二):多系列柱状图

    多系列柱状图大部分与多系列折线图相似 一 简单html布局 简单的html如下
  • SqlServer分页查询

    文章目录 这篇博客讲的是SQL server的分页方法 用的SQL server 2012版本 下面都用pageIndex表示页数 pageSize表示一页包含的记录 并且下面涉及到具体例子的 设定查询第2页 每页含10条记录 首先说一下S
  • 写出质量好软件的75条体会

    1 你们的项目组使用源代码管理工具了么 MVM 应该用 VSS CVS PVCS ClearCase CCC Harvest FireFly都可以 我的选择是VSS 郁也风 公司使用的是VSS 在网上与朋友玩的就是CVS了 咖啡 现在都在用
  • 领域驱动设计:领域事件

    文章目录 领域事件 识别领域事件 领域事件相关案例 领域事件总体架构 领域事件 领域事件是领域模型中非常重要的一部分 用来表示领域中发生的事件 一个领域事件将导致进一步的业务操作 在实现业务解耦的同时 还有助于形成完整的业务闭环 举例来说的
  • 【因子算法】——求一个数的因子、质因子、求两个数的公因子

    下面理清楚一些数学概念 因数 一个数 如果存在可以被它整除的数 则这些数都是该数的因数 规定0没有因数 1的因数是1 其他的比如4的因数有 1 2 4 因子 一个数 如果存在可以被它整除的数且这些数不包括它本身 则这些书都是该数的因子 规定
  • 异常处理:System.Xml.XmlException_缺少根元素

    System Xml XmlException 类型的未处理的异常在System Xml dll中发生 其他 缺少根元素 问题背景 可能是因为强制断电 导致电脑里文件出错 原本运行正常的程序出现报错 缺少根元素 处理方法 删除C Users
  • 使用 sCrypt 实现定期支付合约

    这里我们介绍一个定期支付合约 允许用户定期存入款项 并且收款者可以定期收取款项 合约实现 合约代码如下 contract Recurring Ripemd160 userPubKeyHash Address of the owner of
  • 【uni-app框架】Vue框架的生命周期之data属性、props属性、及其生命周期函数的执行顺序总结【兼容uni-app总结】

    第一 props参数是实时更新的 而created和data仅会执行一次 当每次重新跳转到当前页面的时候 这也是为什么叫做当前页面或当前组件声明周期 第二 在uni app框架中 APP端和H5端的一些区别 APP端不兼容的特性 H5是最权
  • echarts 重新渲染(重新绘制,重新加载数据)等

    转载于 https www cnblogs com Skrillex p 7904797 html
  • SVN版本控制与分支设置

    使用SVN Eclipse做软件版本控制 http cnshell blog sohu com 157502394 html
  • uvm_info信息定制

    1 uvm自带的打印信息国语繁重 不利于debug uvm info TESTCASE sformatf my case0 new UVM DEBUG UVM INFO home zl Desktop uvm study template
  • 算法分析与设计-分治算法

    在计算机科学中 分治法是一种很重要的算法 字面上的解释是 分而治之 就是把一个复杂的问题分成两个或更多的相同或相似的子问题 再把子问题分成更小的子问题 直到最后子问题可以简单的直接求解 原问题的解即子问题的解的合并 这个技巧是很多高效算法的
  • DSA数字签名算法及其实现

    一 实验目的 在掌握了ElGamal和Schorr数字签名算法的基础上 进一步地学习和掌握DSA签名算法 深入地理解该算法是如何降低了签名信息的长度 当其中一个重要参数选为512bit的素数时 ElGamal签名的长度为1024bit 而D
  • 【react】props的简写方式

    props的简写 用static 就可以把类的属性写在类的里面 div div div div
  • linux(centos)无中文输入,如何解决

    1 终端执行安装命令 yum install Chinese Support 2 如下图 多出Input method 3 点击进行配置 4 reboot重启系统 新建一个文本 试一下输入 问题出来 难道是当时新建虚拟机时没有选择增强型虚拟
  • 【Linux】僵尸进程的检测,清理和避免

    一 僵尸进程的产生 一个进程终止的方法很多 进程终止后有些信息对于父进程和内核还是很有用的 例如进程的ID号 进程的退出状态 进程运行的CPU时间等 因此进程在终止时 回收所有内核分配给它的内存 关闭它打开的所有文件等等 但是还会保留以上极
  • 九章算法

    两个排序的数组A和B分别含有m和n个数 找到两个排序数组的中位数 要求时间复杂度应为O log m n 在线评测地址 LintCode 领扣 说明 中位数的定义 这里的中位数等同于数学定义里的中位数 中位数是排序后数组的中间值 如果有数组中