乘风破浪:LeetCode真题_004_Median of Two Sorted Arrays
乘风破浪:LeetCode真题_004_Median of Two Sorted Arrays
一、前言
说到算法,最难的就是一些需要通过分析得到一些递推公式或者有用的结论,进而用来解决问题的方法了。这些东西一方面比较难以理解,另一方面都是有着严密的数学逻辑在里面的,并且需要通过程序来实现,无疑增加了求解的困难。就比如说这次的问题,在两个已经排好序的数组之中找到中间值。这里就涉及到两个数组总长度是奇数还是偶数,如何查找的问题了。
二、Median of Two Sorted Arrays
2.1 问题理解
2.2 分析解决
通过对题目的研究我们能够理解如果是奇数,则不用经过运算即可,如果是偶数,则需要将中间的两个数取平均值。最简单的想法就是将两个数组合并成一个数组,然后求中位数即可。但是这样的方式对于两个已经排好序的数组,需要遍历O(m+n)次,然后才能确定,其中m,n分别为两个已经排好序数组的长度。而题目要求算法时间复杂度为O(log2(m+n)),因此不能使用,那么我们要怎么做呢??
我们先来看看官方的算法:
首先进行分析,假设m<=n,两个数组A,B从小到大排列,那么如果我们分别对两个数组进行某种分割,分割的指针分别为i,j,并且分割之后要达到的结果是分成了四份,左边的两份A1,B1和在一起的长度等于右边A2,B2合在一起的长度,或者(+1),这样我们就找到了中间的几个数,但是我们还需要保证左边的所有元素的最大值要小于或者等于右边所有元素的最小值,如果能保证这两个条件,最终的结果就是左边所有的最大值与右边所有的最小值的平均值,或者左边/右边的最大值/最小值,具体看左边还是右边需要根据算法来考虑。这样就得到了解决问题的思路,可以使用二分查找算法来折半查找,同时也要注意一些边界的条件。
1 class Solution { 2 public double findMedianSortedArrays(int[] A, int[] B) { 3 int m = A.length; 4 int n = B.length; 5 if (m > n) { // to ensure m<=n 6 int[] temp = A; A = B; B = temp; 7 int tmp = m; m = n; n = tmp; 8 } 9 int iMin = 0, iMax = m, halfLen = (m + n + 1) / 2; 10 while (iMin <= iMax) { 11 int i = (iMin + iMax) / 2; 12 int j = halfLen - i; 13 if (i < iMax && B[j-1] > A[i]){ 14 iMin = i + 1; // i is too small 15 } 16 else if (i > iMin && A[i-1] > B[j]) { 17 iMax = i - 1; // i is too big 18 } 19 else { // i is perfect 20 int maxLeft = 0; 21 if (i == 0) { maxLeft = B[j-1]; } 22 else if (j == 0) { maxLeft = A[i-1]; } 23 else { maxLeft = Math.max(A[i-1], B[j-1]); } 24 if ( (m + n) % 2 == 1 ) { return maxLeft; } 25 26 int minRight = 0; 27 if (i == m) { minRight = B[j]; } 28 else if (j == n) { minRight = A[i]; } 29 else { minRight = Math.min(B[j], A[i]); } 30 31 return (maxLeft + minRight) / 2.0; 32 } 33 } 34 return 0.0; 35 } 36 }
下面来看看我们的思路:
public class Solution { /** * <pre> * There are two sorted arrays nums1 and nums2 of size m and n respectively. * Find the median of the two sorted arrays. The overall run time complexity * should be O(log (m+n)). * * 题目大意: * 两个排序数组,找这两个排序数组的中位数,时间复杂度为O(log(m+n)) * * 题解思路: * 递归分治求解 * </pre> * * @param nums1 * @param nums2 * @return */ public double findMedianSortedArrays(int[] nums1, int[] nums2) { if (nums1 == null) { nums1 = new int[0]; } if (nums2 == null) { nums2 = new int[0]; } int len1 = nums1.length; int len2 = nums2.length; if (len1 < len2) { // 确保第一个数组比第二个数组长度大 return findMedianSortedArrays(nums2, nums1); } // 如果长度小的数组长度为0,就返回前一个数组的中位数 if (len2 == 0) { return (nums1[(len1 - 1) / 2] + nums1[len1 / 2]) / 2.0; } int lo = 0; int hi = len2 * 2; int mid1; int mid2; double l1; double l2; double r1; double r2; while (lo <= hi) { mid2 = (lo + hi) / 2; mid1 = len1 + len2 - mid2; l1 = (mid1 == 0) ? Integer.MIN_VALUE : nums1[(mid1 - 1) / 2]; l2 = (mid2 == 0) ? Integer.MIN_VALUE : nums2[(mid2 - 1) / 2]; r1 = (mid1 == len1 * 2) ? Integer.MAX_VALUE : nums1[mid1 / 2]; r2 = (mid2 == len2 * 2) ? Integer.MAX_VALUE : nums2[mid2 / 2]; if (l1 > r2) { lo = mid2 + 1; } else if (l2 > r1) { hi = mid2 - 1; } else { return (Math.max(l1, l2) + Math.min(r1, r2)) / 2; } } return -1; } }
注意我们这里的算法和官方的原理一样,只不过是将分割的坐标都扩大了两倍,这样更加能优化算法的性能,并且因为扩大了2倍之后,其他地方也做了相应的改变而已。
三、总结
遇到这样的问题,最重要的是要学会如何去建模,然后进行解决,涉及到了一定的算法和逻辑思维能力,对分析和理解都有很大的考察价值。