第四道题,题目如下:
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)).
For example,
nums1 = [1, 3] nums2 = [2] The median is 2.0
nums1 = [1, 2] nums2 = [3, 4] The median is (2 + 3)/2 = 2.5
这道题难在了最后一句有对运行时间的限制,明确的说希望总共的运行时间在O(log(m+n))范围内,但是leetcode的编译器好像没有把这个限制体现出来,导致我第一次写的这个代码也通过了编译,由于nums1和nums2本身已经是排序好的数组了,可以先把这两个数组先合并成一个新的数组,然后找到这个新的数组的中位数,使用javascript代码描述如下:
1 var findMedianSortedArrays = function(nums1, nums2) { 2 var result=[]; 3 var i=0 4 var j=0; 5 while(i<nums1.length && j<nums2.length){ 6 if(nums1[i]<nums2[j]){ 7 result.push(nums1[i]); 8 i++; 9 } 10 else{ 11 result.push(nums2[j]); 12 j++; 13 } 14 } 15 16 while(i<nums1.length){ 17 result.push(nums1[i]); 18 i++ 19 } 20 21 while(j<nums2.length){ 22 result.push(nums2[j]) 23 j++; 24 } 25 26 if(result.length%2==1){ 27 return result[(result.length-1)/2]; 28 } 29 else{ 30 var tmp=result[result.length/2-1]+result[result.length/2]; 31 return tmp/2; 32 } 33 };
这个方法是最简单粗暴的方法,但是仔细看这个的时间复杂度是O(max(n,m)),不符合题目中的时间限制。
既然是求中位数,那么具体中位数所在的位置是知道的,如果nums1,nums2两个数组的长度和是奇数,则表示如果把这两个数组合并,中位数的位置在下标为(nums1.length+nums2.length-1)/2的地方,如果nums1,nums2两个数组的长度和是偶数,则合并后的中位数是下标为(nums1.length+nums2.length)/2-1和下标为(nums1.length+nums2.length)/2的这两个数的平均数。所以主要的问题就是去找合并后对应下标的元素就可以了,也就是当给出两个已经排好序的数组,返回两个数组合并后下标为k的元素。
采取类似二分法的做法,分别比较两个数组中位于下标k/2处的元素,每次判断nums1[k/2]和nums2[k/2]的大小,一次可以去掉k/2+1个元素不用考虑。
有以下情况:
1)在保证nums1始终为长度较短的那个数组的情况下,如果nums1的长度是0,则直接返回nums2[k]
2)如果k为0,则返回nums1[0]和nums2[0]中较小的那个
3)如果k/2大于nums1的最后一个元素的下标值,则令p1=nums1.length-1,否则p1=k/2,p2=k-p1-1
i)如果nums1[p1]>nums2[p2],应该忽略nums2的前p2+1个元素,则把nums2.slice(p2+1)作为参数递归返回,此时中位数的下标变成了k-p2-1
ii)如果nums1[p1]<nums2[p2],应该忽略nums1的前p1+1个元素,则把nums2.slice(p1+1)作为参数递归返回,此时中位数的下标变成了k-p1-1
直到k=0或者某个数组的长度变为0就会返回具体的值。
javascript代码描述如下:
1 var findMedianSortedArrays = function(nums1, nums2) { 2 var length=nums1.length+nums2.length; 3 if(length%2===0){ 4 return (findKth(nums1,nums1.length,nums2,nums2.length,length/2-1)
+findKth(nums1,nums1.length,nums2,nums2.length,length/2))/2; 5 } 6 else{ 7 return findKth(nums1,nums1.length,nums2,nums2.length,(length-1)/2); 8 } 9 }; 10 11 function findKth(nums1,length1,nums2,length2,k){ 12 if(length1>length2){ 13 return findKth(nums2,length2,nums1,length1,k); 14 } 15 if(length1===0){ 16 return nums2[k]; 17 } 18 if(k===0){ 19 return nums1[0]>nums2[0]?nums2[0]:nums1[0]; 20 } 21 var p1=parseInt(k/2)>(length1-1)?(length1-1):parseInt(k/2); 22 var p2=k-p1-1; 23 if(nums1[p1]>nums2[p2]){ 24 nums2=nums2.slice(p2+1); 25 return findKth(nums1,nums1.length,nums2,nums2.length,k-p2-1); 26 } 27 else{ 28 nums1=nums1.slice(p1+1); 29 return findKth(nums1,nums1.length,nums2,nums2.length,k-p1-1); 30 } 31 32 }
同理,使用C++代码描述如下:
1 class Solution { 2 public: 3 double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) { 4 int length=nums1.size()+nums2.size(); 5 if(length%2==0){ 6 return (findKth(nums1,nums1.size(),nums2,nums2.size(),length/2-1) 7 +findKth(nums1,nums1.size(),nums2,nums2.size(),length/2))/2; 8 } 9 else{ 10 return findKth(nums1,nums1.size(),nums2,nums2.size(),(length-1)/2); 11 } 12 } 13 14 double findKth(vector<int> nums1,int length1,vector<int> nums2,int length2,int k){ 15 if(length1>length2){ 16 return findKth(nums2,length2,nums1,length1,k); 17 } 18 if(length1==0){ 19 return nums2[k]; 20 } 21 if(k==0){ 22 return min(nums1[0],nums2[0]); 23 } 24 int p1=(int)(k/2)>length1-1?length1-1:(int)(k/2); 25 int p2=k-p1-1; 26 if(nums1[p1]<nums2[p2]){ 27 nums1.assign(nums1.begin()+p1+1,nums1.end()); 28 return findKth(nums1,length1-p1-1,nums2,length2,k-p1-1); 29 } 30 else{ 31 nums2.assign(nums2.begin()+p2+1,nums2.end()); 32 return findKth(nums1,length1,nums2,length2-p2-1,k-p2-1); 33 } 34 }
有关C++知识点总结:
1)区分函数传引用,传值,传指针,函数传值表示函数中对参数的更改不会体现在原始的数据中,只在函数范围内有效,而函数传引用则表示函数中对参数的操作始终和原始数据保持一致,也就是说函数中对参数的所有改变都会体现在原始数据中,而传指针和传引用差不多,把该原始数据所在的地址当作参数进行传递,此时在函数中进行的修改就是直接对原始数据进行修改。
根据上面的说法,findKth函数中的vector参数需要用传值的方式,因为在findKth函数中对vector进行了assign的截取操作,如果传引用则会影响在偶数情况下的取值操作。
2)vector的assign方法,可以作为截取vector中的元素来使用,具体的用法解释是:可以将区间[first,last)的元素赋值到当前的vector容器中,或者赋n个值为x的元素到vector容器中,这个容器会清除掉vector容器中以前的内容。
vector<int> a; a.assign(a.begin()+1,a.end()); //表示从a的第二个元素开始截取到最后一个元素重新赋值给a a.assign(10,3); //表示把原来的容器a清空后,重新初始化为10个3
1 vector<int> a; 2 a.push_back(1); 3 a.push_back(2); 4 a.push_back(3); 5 vector<int> b; 6 b.assign(a.begin()+1, a.end()); //[2,3]
有关javascript的知识点总结:
由于javascript的函数都是传值,不存在传引用,所以,就没有像C++那样的考虑,
数组的截取使用方法array.slice(start,end)返回在[start,end)范围内的元素组成的新数组,由于end参数可以省略,如果只有start没有end则默认到原来数组的结尾。
区别数组操作的splice和slice两个方法:
array.slice(start,end) 从数组array中返回指定范围内的元素,注意slice操作不会改变原来array的内容而是会返回一个新的子数组
1 var a=[1,2,3,4,5,6,7,8,9]; 2 console.log(a.slice(0,3)); //[1,2,3] 3 console.log(a); //[1,2,3,4,5,6,7,8,9]
array.splice(index,howmany,item1,.....,itemX):表示删除以下标为index开始的howmany个元素,item1...itemX为可选参数,表示使用item1..itemX来代替那些删除的元素。
1 var a=[1,2,3,4,5,6,7,8,9]; 2 a.splice(0,3); 3 console.log(a); //[4,5,6,7,8,9]
1 var a=[1,2,3,4,5,6,7,8,9]; 2 a.splice(0,3,10,11); 3 console.log(a); //[10,11,4,5,6,7,8,9]