LeetCode刷题笔记4寻找两个正序数组的中位数

public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int length1 = nums1.length, length2 = nums2.length;
int totalLength = length1 + length2;
if (totalLength % 2 == 1) {
int midIndex = totalLength / 2;
double median = getKthElement(nums1, nums2, midIndex + 1);
return median;
} else {
int midIndex1 = totalLength / 2 - 1, midIndex2 = totalLength / 2;
double median = (getKthElement(nums1, nums2, midIndex1 + 1) + getKthElement(nums1, nums2, midIndex2 + 1)) / 2.0;
return median;
}
}

public int getKthElement(int[] nums1, int[] nums2, int k) {
/* 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较

  • 这里的 “/” 表示整除
  • nums1 中小于等于 pivot1 的元素有 nums1[0 … k/2-2] 共计 k/2-1 个
  • nums2 中小于等于 pivot2 的元素有 nums2[0 … k/2-2] 共计 k/2-1 个
  • 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个
  • 这样 pivot 本身最大也只能是第 k-1 小的元素
  • 如果 pivot = pivot1,那么 nums1[0 … k/2-1] 都不可能是第 k 小的元素。把这些元素全部 “删除”,剩下的作为新的 nums1 数组
  • 如果 pivot = pivot2,那么 nums2[0 … k/2-1] 都不可能是第 k 小的元素。把这些元素全部 “删除”,剩下的作为新的 nums2 数组
  • 由于我们 “删除” 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数
    */

int length1 = nums1.length, length2 = nums2.length;
int index1 = 0, index2 = 0;
int kthElement = 0;

while (true) {
// 边界情况
if (index1 == length1) {
return nums2[index2 + k - 1];
}
if (index2 == length2) {
return nums1[index1 + k - 1];
}
if (k == 1) {
return Math.min(nums1[index1], nums2[index2]);
}

// 正常情况
int half = k / 2;
int newIndex1 = Math.min(index1 + half, length1) - 1;
int newIndex2 = Math.min(index2 + half, length2) - 1;
int pivot1 = nums1[newIndex1], pivot2 = nums2[newIndex2];
if (pivot1 <= pivot2) {
k -= (newIndex1 - index1 + 1);
index1 = newIndex1 + 1;
} else {
k -= (newIndex2 - index2 + 1);
index2 = newIndex2 + 1;
}
}
}

官方题解:划分数组

相比较与二分查找,划分数组的官方题解确实有一定的理解难度。我这里不打算按照官方题解来解释这个问题。希望能够更加容易理解这个方法。

假设我们将两个数组合并成一个有序数组,并且按照下面的规则划分,

当总是是奇数,左侧的长度比右侧多1 ,当总数是偶数 左右两侧长度相等 即奇数时,中位数是左侧的最大值,偶数时是左侧最大值与右侧最小值的平均数。

官方划分数组.png

在合并后的数组中,前(m+n)/2中的数如果有 i 个来自数组A那么剩余的 j = m+n)/2 - i个必然来自n。

举例说明:(m+n)/2 = 6

当 i = 0 时 j = 6 即前6个全部来自B数组,左侧最大值为B[5] = 30 > 右侧最小值A[0] = 1 不能构成有序数组, 不符合

当 i = 1 时 j = 5 左侧最大值是 A[0] B[4] 中的较大值;B4=[15] > 右侧最小值A[1] = 3 (B[5] = 30 > A[1]) 不能构成有序数组, 不符合

当 i = 2 时 j = 4 左侧最大值为B[3] = 12 右侧最小值是 A[2] = 6 不符合

当 i = 3 时 j = 3 左侧最大值是 B[2] = 9 右侧最小值是 A[3] = 8 不符合

当 i = 4 时 j = 2 左侧最大致是 A[3] = 8 右侧最小值是 B[2] = 9 符合 因为 两个数组总长度是偶数 中位数是 (左侧最大 + 右侧最小) / 2 .0= (8 + 9) /2.0 = 8.5

明显的此时的时间复杂度为O(m) 不符合 O(log (m+n)) 的算法。

采用二分查找法来进行处理,以数组长度较小的作为定准。为什么不能以较大的呢?假设m = 20 n = 2;因为二分查找, i 的取值范围是[0,20],k = (m+2)/2 = 11 如果i取大于11的数那么j就只能取负数,明显是不合理的。因此需要用较小长度的数组作为定准。

既然采用二分法来查找i的取值,那么什么时候 这个范围向上,什么时候范围向下呢?

我们继续补全上面案例中的数据

i = 5 时 j = 1 左侧最大数 A[4] = 12 右侧最小数为 B[1] = 6 不符合

i = 6 时 j = 0 左侧最大数 A[5] = 19 右侧最小数B[0] = 1 不符合

可以发现,当A[i] <= B[j-1]的时候 i 还可以增加。否则只能减小。

数组AB都有序,i 减小,j会增加,如果此时再去减小i 那么A[i] <= B[j-1]会永远为true,只有增加I减小j。

这样我们就能理解官方的数组划分的方法了。

官方参考代码:

public double findMedianSortedArrays(int[] nums1, int[] nums2) {
if (nums1.length > nums2.length) {
return findMedianSortedArrays(nums2, nums1);
}

int m = nums1.length;
int n = nums2.length;
int left = 0, right = m;
// median1:前一部分的最大值
// median2:后一部分的最小值
int median1 = 0, median2 = 0;

while (left <= right) {
// 前一部分包含 nums1[0 … i-1] 和 nums2[0 … j-1]
// 后一部分包含 nums1[i … m-1] 和 nums2[j … n-1]
int i = (left + right) / 2;
int j = (m + n + 1) / 2 - i;

// nums_im1, nums_i, nums_jm1, nums_j 分别表示 nums1[i-1], nums1[i], nums2[j-1], nums2[j]
int nums_im1 = (i == 0 ? Integer.MIN_VALUE : nums1[i - 1]);
int nums_i = (i == m ? Integer.MAX_VALUE : nums1[i]);
int nums_jm1 = (j == 0 ? Integer.MIN_VALUE : nums2[j - 1]);
int nums_j = (j == n ? Integer.MAX_VALUE : nums2[j]);
if (nums_im1 <= nums_j) {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

最后

我这里整理了一份完整的学习思维以及Android开发知识大全PDF。

当然实践出真知,即使有了学习线路也要注重实践,学习过的内容只有结合实操才算是真正的掌握。

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

C大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算**

  • 26
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值