题目:两个有序数组nums1和nums2,长度分别为m和n,0<m<10000,0<n<10000,1<m+n<10000,求两个数组的中位数。
方法1:先归并排序,然后求有序数组第K和第K-1大元素(K=(m+n)/2)。时间复杂度O(m+n),空间复杂度O(m+n)。
public class FindMedainOfTwoOrderedArrays{
public static int[] MergeSort(int[] nums1, int[] nums2){
int nums1Length = nums1.length;
int nums2Length = nums2.length;
int[] numsOfTwoArraysMerge = new int[nums1Length + nums2Length];
int i = 0;
int j = 0;
int k = 0;
while (i<nums1Length&&j<nums2Length){
if (nums1[i] < nums2[j]){
numsOfTwoArraysMerge[k] = nums1[i];
i++;
k++;
}else{
numsOfTwoArraysMerge[k] = nums2[j];
j++;
k++;
}
}
while (i < nums1Length){
numsOfTwoArraysMerge[k] = nums1[i];
i++;
k++;
}
while (j < nums1Length){
numsOfTwoArraysMerge[k] = nums1[j];
j++;
k++;
}
return numsOfTwoArraysMerge;
}
public static void main(String[] args){
int[] nums1 = {1, 2, 3, 8,10};
int[] nums2 = {2, 4, 5, 9, 19};
int[] nums = MergeSort(nums1, nums2);
int nums1Length = nums1.length;
int nums2Length = nums2.length;
int k = (nums1Length + nums2Length)/2;
if ((nums1Length + nums2Length)%2 == 1){
System.out.println(nums[k]);
}else{
System.out.println((nums[k-1] + nums[k])/2.0);
}
}
}
方法二:转化为求第K大元素。K=(m+n)/2,half=K/2,在nums1中取half个元素,在nums2中取(k-half)个元素(如果其中一个数组不够取,那就取出该数组剩下所有元素),这里假设两个数组都能够取出half个元素。
左边一共有K个元素:
|nums1[0],…nums1[half-1] || nums1[half],…nums1[nums1.length-1]|
|nums2[0],…nums2[K-half-1] || nums2[K-half],…nums2[nums2.length-1]|
此时必有有num1[half-1]>nums1[half-2]和nums2[K-half-1]>nums2[K-half-2],若nums1[half-1]<nums2[K-half-1],那么已经没有K-1个元素比nums1[half-1]小,所以nums1中nums1[half-1]以及它前面的数据不可能是第K大数,去除nums1中nums1[half-1]以及它前面的数据;若nums1[half-1]>nums2[K-half-1],同理;nums1[half-1]=nums2[K-half-1]可以归为上面任意一种情况。
(1)K=(m+n),half=K/2,在nums1中取half个元素,在nums2中取(k-half)个元素,此时nums1和nums2中一共取出来K个元素。数组nums1和nums2都是有序数组,有nums1[half-1]>nums1[half-2],nums2[K-half-1]>nums2[K-half-2];
(2)第K大元素必然存在K-1个元素比它小。若nums1[half-1] > nums2[K-half-1],此时nums2[K-half-1]已经不存在K-1个元素比它小,所以可以排除nums2中nums2[K-half-1]和它之前的数据,同时K值减去已经排除数据的数量;若nums2[K-half-1] >nums1[half-1],同上分析;当nums1[half-1]=nums2[K-half-1],可以归为上面任何一种情况。
(3)结束条件。第一,当nums1或nums2中的元素已经取完,此时取还没有取完数组的第K-1个元素;第二,在nums1和nums2中的元素没有取完的时候,K=1,此时相当于取现存数组中第K大元素,就是最小一个元素,这个时候取两个数组中剩余元素最左边的最小值。
时间复杂度O(log(m+n)),空间复杂度O(1)
public class FindMedainOfTwoOrderedArrays{
public static int getKthElement(int[] nums1, int[] nums2, int k){
int nums1Left = 0;
int nums2Left = 0;
int nums1Right = nums1.length;
int nums2Right = nums2.length;
int nums1Medain = 0;
int nums2Medain = 0;
while (true){
if (nums1Left == nums1Right){
return nums2[nums2Left + k - 1];
}
if (nums2Left == nums2Right){
return nums1[nums1Left + k -1];
}
if (k == 1){
return Math.min(nums1[nums1Left], nums2[nums2Left]);
}
int half = k/2;
nums1Medain = Math.min(nums1Left + half - 1, nums1Right - 1);
nums2Medain = Math.min(nums2Left + half - 1, nums2Right - 1);
if (nums1[nums1Medain] > nums2[nums2Medain]){
k = k - (nums2Medain - nums2Left + 1);
nums2Left = nums2Medain + 1;
}else{
k = k - (nums1Medain - nums1Left + 1);
nums1Left = nums1Medain + 1;
}
}
}
public static void main(String[] args){
int[] nums1 = {1, 3, 4, 6, 12};
int[] nums2 = {3, 4, 8, 23 ,67};
int totalLength = nums1.length + nums2.length;
int k = totalLength/2;
if (totalLength%2 == 1){
System.out.println(getKthElement(nums1, nums2, k+1));
}else{
System.out.println((getKthElement(nums1, nums2, k) + getKthElement(nums1, nums2, k + 1))/2);
}
}
}
方法三:对m和n中小值进行二分。中位数的作用是使右边的所有数据总是大于左边的数据。
K=(m+n)/2,假设m<=n。
当m+n为偶数时:
|左边一共有K个元素,右边有K个元素 |
|nums1[0],…nums1[m/2-1] || nums1[m/2],…nums1[nums1.length-1]|
|nums2[0],…nums2[K-m/2-1] || nums2[K-m/2],…nums2[nums2.length-1]|
当min(nums1[m/2], nums2[K-m/2]) > max(nums1[m/2-1], nums2[K-m/2-1]),那么中位数就是(min(nums1[m/2], nums2[K-m/2]) + max(nums1[m/2-1], nums2[K-m/2-1]))/2.0;
当m+n为奇数时:
|左边一共有K个元素,右边有K+1个元素 |
|nums1[0],…nums1[m/2-1] || nums1[m/2],…nums1[nums1.length-1]|
|nums2[0],…nums2[K-m/2-1] || nums2[K-m/2],…nums2[nums2.length-1]|
当min(nums1[m/2], nums2[K-m/2]) > max(nums1[m/2-1], nums2[K-m/2-1]),那么中位数就是min(nums1[m/2], nums2[K-m/2])。
(1)当m<n,K=(m+n)/2,在nums1中取出m/2个元素,在nums2中取出K-m/2个元素;
(2)当nums1[m/2-1]>=nums2[K-m/2-1]时,第K大元素只需要有K-1个元素比它小,此时nums1[m/2-1]前面至少有K-1个元素比它小,所以它后面的数据已经不能为第K大元素,排除nums1中nums1[m/2-1]之后的数据,同时K减去已经排除的数据的数量;当nums1[m/2-1]<nums2[K-m/2-1]时,第K大元素需要有K个元素比它小,此时nums1[m/2-1]已经不可能有K-1个元素比它小,所以nums1中nums1[m/2-1]以及它之前的数据可以排除,同时K减去已经排除数据的数量。
(3)结束条件。当min(nums1[m/2], nums2[K-m/2]) > max(nums1[m/2-1], nums2[K-m/2-1])时,找到中位数,返回按照奇偶计算返回中位数;
时间复杂度O(min(m, n)),空间复杂度O(1)。
public class FindMedainOfTwoOrderedArrays{
public static double findMedainByBinaryArray(int[] nums1, int[] nums2){
if (nums1.length < nums2.length){
return findMedainByBinaryArray(nums2, nums1);
}
int nums1Left = 0;
int nums2Left = 0;
int nums1Right = nums1.length;
int nums2Right = nums2.length;
int totalLength = nums1.length + nums2.length;
int k = totalLength/2;
int nums1Medain = 0;
int nums2Medain = 0;
while (true){
int half = (nums1Right - nums1Left)/2;
nums1Medain = nums1Left + half - 1;
nums2Medain = nums2Left + (k - (nums1Left+half)) - 1;
int rightMin = Integer.MAX_VALUE;
if (nums1Medain < nums1Right - 1){
rightMin = Math.min(rightMin, nums1[nums1Medain+1]);
}
if (nums2Medain < nums2Right - 1){
rightMin = Math.min(rightMin, nums2[nums2Medain+1]);
}
int leftMax = Math.max(nums1[nums1Medain], nums2[nums2Medain]);
if (leftMax < rightMin){
if (totalLength%2 == 1){
return rightMin;
}else{
return (leftMax + rightMin)/2.0;
}
}
if (nums1[nums1Medain] > nums2[nums2Medain]){
nums1Right = nums1Left + 1;
}else{
nums1Left = nums1Medain + 1;
}
}
}
public static void main(String[] args){
int[] nums1 = {1, 3, 4, 6, 12, 23};
int[] nums2 = {3, 4, 8, 23 ,67};
System.out.println(findMedainByBinaryArray(nums1, nums2));
}
}