2021-10-04

题目:两个有序数组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));
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值