LeetCode 4. 寻找两个正序数组的中位数 | 算法-从菜鸟开始

简介: 本文是《算法-从菜鸟开始》系列文章的第5篇,欢迎收藏、留言、点赞。话不多说,让我们继续我们的算法之旅。

一、LeetCode 4. 寻找两个正序数组的中位数


题目介绍:


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


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


示例:


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


解题思路


从题目上来分析,有两个要求要满足:


  1. 要查找出两个数组nums1nums2的中位数


  1. 要满足算法的时间复杂度是 O(log(m+n))O(log(m+n))O(log(m+n))

我们逐一来看。


数组的中位数:即两个数组合并后,取数组最中间的一个(数组长度为奇数)或两个数(数组长度为偶数)的平均数。 并且已知数组nums1nums2是正序(即由大到小)排列的。


那我们需要把两个数组进行从小到大的完全合并、排序,然后再查找、计算这个中位数吗?


其实并不需要,因为都是两个数组从小到大排列的,所以我们仅查找出符合要求的最中间一个或两个数。


我们可以设置一个数组nums,将所给数组nums1nums2中的值按小到达放入,直到符合的长度。


这个数组nums所需的长度计算规则:


// 根据数组的长度以及是偶数还是奇数,我们可以计算出需要的 - 基数取最中间的一位,偶数取最中间的两位
const maxNumsLength = len % 2 === 0 ? len / 2 + 1 : Math.ceil(len / 2);
// 加入输入的是 [1, 3] [2],通过计算可得maxNumsLength值为2
// 则nums中需要存入 [1, 2]


我们来看下代码的实现:


/**
 * @param {number[]} nums1
 * @param {number[]} nums2
 * @return {number}
 */
var findMedianSortedArrays = function(nums1, nums2) {
  // 获取两个数组的长度和
  const len = nums1.length + nums2.length;
  // 根据数组的长度以及是偶数还是奇数,我们可以计算出需要的 - 基数取一位,偶数取两位
  const maxNumsLength = len % 2 === 0 ? len / 2 + 1 : Math.ceil(len / 2);
  // 设置nums存储元素
  const nums = [];
  // 每次取出各自数组中的最小值(即数组的首位子元素),进行比较,将元素放入到nums中,并且将原数组中的该元素移除。
  // 每次存储nums1的首位
  let nums1First = nums1.shift();
  // 每次存储nums2的首位
  let nums2First = nums2.shift();
  // 该循环的条件为 nums.length < maxNumsLenth 并且nums1First 或者nums2First有值存在
  while ( (nums1First !== undefined || nums2First !== undefined) && nums.length < maxNumsLength) {
    // 边界校验:当nums1First为undefined时,表示此时的nums1数组已经空了,只存储nums2的元素即可
    if (nums1First === undefined) {
        nums.unshift(nums2First);
        nums2First = nums2.shift();
        continue;
    }
    // 边界校验:当nums2First为undefined时,表示此时的nums2数组已经空了,只存储nums1的元素即可
    if (nums2First === undefined) {
        nums.unshift(nums1First);
        nums1First = nums1.shift();
        continue;
    }
    // 不为空时,比较二者大小,取最小值,放入数组nums中
    if (nums1First > nums2First) {
      nums.unshift(nums2First);
      nums2First = nums2.shift();
    } else {
      nums.unshift(nums1First);
      nums1First = nums1.shift();
    }
  }
  // 中位数
  let median;
  // 如果是偶数取前两个否则取第一个
  if (len % 2 === 0) {
    median = (nums[1] + nums[0]) / 2;
  } else {
    median = nums[0];
  }
  // 保留5位小数
  return middle.toFixed(5)
};


提交代码,我们看下执行情况,从执行用时来看,这个算法实现的还不错。


网络异常,图片无法展示
|


但是,这样的算法实现达到了题目要求的O(log(m+n))O(log(m+n))O(log(m+n))了吗?

其实并没有,看下现在的时间复杂度是 O((m+n)2+1)O(\tfrac{(m+n)}{2} + 1)O(2(m+n)+1),即O(m+n)O(m+n)O(m+n)


二、二分法查找


二分法查找,是指在一组有序数组查找某个指定元素的算法。


每次查找的开始都是数组的二分之一处开始,查看是否符合要求。如果不符合,则向上或者向下进行查找。


举例说明:在数组 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]中查找数字10的索引。

首先使用常规算法:枚举比对


const findKey = (nums, target) => {
  // 遍历nums的所有元素
  for (let i = 0; i < nums.length; i++) {
    if (nums[i] === target){
      return i;
    }
  }
}
// 调用函数
const index = findKey([1, 2, 3, 5, 6], 6);
// 一共执行了6次比对
console.log(index); // 6


此种算法的时间复杂度是O(n)O(n)O(n)


如果使用二分法查找呢?


二分法的一个前提条件就是数组必需是有序的,可以是正序也可以是逆序。


const halfFindKey = (nums, target) {
  // 声明一个起始位置索引
  let startIndex = 0;
  let endIndex = nums.length - 1;
  let halfIndex;
  do {
    // 取二分之一处位置,考虑是偶数长度还是奇数长度
    // 取二分之一位置
    halfIndex = startIndex + Math.floor((endIndex - startIndex) / 2);
    // 当前值与target比对
    if (target === nums[halfIndex]) {
      return halfIndex
    } else {
      // 比较二者大小
      if (target > nums[halfIndex]) {
        // 说明target在右面
        startIndex = halfIndex + 1;
      } else {
        endIndex = halfIndex - 1;
      }
    }
    // 考虑边界情况,说明只有一个元素了
    if(startIndex === endIndex - 1) {
      // 判断是向上还是向下走
      return halfIndex > startIndex ? startIndex : endIndex;
    }
  } while (halfIndex)
}
// 调用函数
const index = halfFindKey([1, 2, 3, 4, 5, 6], 6);
// 执行3次循环,每次比对的元素分别是 nums[2]、nums[4]、nums[5]
console.log(index); // 3


该算法的时间复杂度是 O(logn)O(logn)O(logn),每次取数组的二分之一进行查询。


现在我们把目光再转移到这个 寻找两个正序数组的中位数问题上。



相关文章
|
2月前
|
算法
Leetcode 初级算法 --- 数组篇
Leetcode 初级算法 --- 数组篇
41 0
|
4月前
|
算法
LeetCode第53题最大子数组和
LeetCode第53题"最大子数组和"的解题方法,利用动态规划思想,通过一次遍历数组,维护到当前元素为止的最大子数组和,有效避免了复杂度更高的暴力解法。
LeetCode第53题最大子数组和
|
29天前
|
存储 算法 Java
leetcode算法题-有效的括号(简单)
【11月更文挑战第5天】本文介绍了 LeetCode 上“有效的括号”这道题的解法。题目要求判断一个只包含括号字符的字符串是否有效。有效字符串需满足左括号必须用相同类型的右括号闭合,并且左括号必须以正确的顺序闭合。解题思路是使用栈数据结构,遍历字符串时将左括号压入栈中,遇到右括号时检查栈顶元素是否匹配。最后根据栈是否为空来判断字符串中的括号是否有效。示例代码包括 Python 和 Java 版本。
|
2月前
|
算法 程序员 索引
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
栈的基本概念、应用场景以及如何使用数组和单链表模拟栈,并展示了如何利用栈和中缀表达式实现一个综合计算器。
39 1
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
|
2月前
|
算法
每日一道算法题(Leetcode 20)
每日一道算法题(Leetcode 20)
29 2
|
2月前
【LeetCode-每日一题】 删除排序数组中的重复项
【LeetCode-每日一题】 删除排序数组中的重复项
21 4
|
2月前
|
存储 算法 定位技术
数据结构与算法学习二、稀疏数组与队列,数组模拟队列,模拟环形队列
这篇文章主要介绍了稀疏数组和队列的概念、应用实例以及如何使用数组模拟队列和环形队列的实现方法。
25 0
数据结构与算法学习二、稀疏数组与队列,数组模拟队列,模拟环形队列
|
2月前
|
索引
Leetcode第三十三题(搜索旋转排序数组)
这篇文章介绍了解决LeetCode第33题“搜索旋转排序数组”的方法,该问题要求在旋转过的升序数组中找到给定目标值的索引,如果存在则返回索引,否则返回-1,文章提供了一个时间复杂度为O(logn)的二分搜索算法实现。
20 0
Leetcode第三十三题(搜索旋转排序数组)
|
2月前
|
算法 C++
Leetcode第53题(最大子数组和)
这篇文章介绍了LeetCode第53题“最大子数组和”的动态规划解法,提供了详细的状态转移方程和C++代码实现,并讨论了其他算法如贪心、分治、改进动态规划和分块累计法。
68 0
|
2月前
|
C++
【LeetCode 12】349.两个数组的交集
【LeetCode 12】349.两个数组的交集
18 0