有序数组寻找中位数以及寻找K大元素
问题描述:
两个排序的数组A和B分别含有m和n个数,找到两个排序数组的中位数,要求时间复杂度应为O(log (m+n))。转化成找到两个数组的第K大数字进行解决
解题方法:
对于一个长度为n的已排序数列a,若n为奇数,中位数为a[n / 2 + 1] , 若n为偶数,则中位数(a[n / 2] + a[n / 2 + 1]) / 2如果我们可以在两个数列中求出第K小的元素,便可以解决该问题不妨设数列A元素个数为n,数列B元素个数为m,各自升序排序,求第k小元素取A[k / 2] B[k / 2] 比较,如果 A[k / 2] > B[k / 2] 那么,所求的元素必然不在B的前k / 2个元素中(证明反证法)反之,必然不在A的前k / 2个元素中,于是我们可以将A或B数列的前k / 2元素删去,求剩下两个数列的k - k / 2小元素,于是得到了数据规模变小的同类问题,递归解决如果 k / 2 大于某数列个数,所求元素必然不在另一数列的前k / 2个元素中,同上操作就好。
递归过程中的边界条件:
1、当两个数组中其中有一个为空,直接返回另外一个的第K个元素
什么时候会出现空,当有一个数组的有效比较长度恰好是K/2个,并且这个位置的元素小于另外一个数组的元素,该数组的有效比较区间发生改变,这个时候会发生一个为空一个不为空的情况,当然也可以在初始时就进行判断。
2、当k == 1时,返回两个数组起始位置的较小值
另外,在递归取每个数组的第K/2大的元素时,可能数组中A的元素已经不够了,这个时候两个元素的diK大必然在A和另外一个数组的后K/2的位置里,此时可以将A的比较元素设置成为无穷大,保证将另外的数组的前K/2个元素删去。
当其中一个数组中的元素不够K/2时,设其长度为L2,那么L2 + L1(其实就是K/2) < K必然成立,那么对于上面的数组来说寻找的第K大就不可能在前面的L1中,必然在两个黑色的部分中,此时将上面的数组的前K/2个元素去除即可;
//看做是找到两个序列的第K大个数的问题,如果是奇数个数字那么就是找到一个, //如果是偶数,那么就找找K个和K+1个数字 class Solution { public: double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) { int totalLength = nums1.size() + nums2.size(); double res; if(totalLength & 1){ res = findKth(nums1, 0, nums2, 0, totalLength/2 + 1); }else{ double temp1 = findKth(nums1, 0, nums2, 0, totalLength/2); double temp2 = findKth(nums1, 0, nums2, 0, totalLength/2 + 1); res = (temp1 + temp2) / 2.0; } return res; } double findKth(vector<int>& nums1, int start1, vector<int>& nums2, int start2, int index){ if(start1 >= nums1.size()){ return (double)nums2[start2 + index - 1]; } if(start2 >= nums2.size()){ return (double)nums1[start1 + index - 1]; } if(index == 1){ return min(nums1[start1], nums2[start2]); } int nums1_key = start1 + index / 2 - 1 >= nums1.size()? INT_MAX : nums1[start1 + index / 2 - 1]; int nums2_key = start2 + index / 2 - 1 >= nums2.size()? INT_MAX : nums2[start2 + index / 2 - 1]; if(nums1_key < nums2_key){ return findKth(nums1, start1 + index / 2, nums2, start2, index - index / 2); }else{ return findKth(nums1, start1, nums2, start2 + index / 2, index - index / 2); } } };
此外在找到两个数组的第K大数字时,除了可以使用上面的额方法,还可以使用小顶堆的方式,借助堆数据结果来实现,这样的方法是O(n)级别的,但是对于像链表之类的不提供随机读写的数据结构来说只能使用小顶堆的方法解决。
除了O(n)的计算方法之外还有一种比较笨重的方法就是先把这两个目标merge到一起,然后直接去找也可以,对于多个数组或者多个链表来说就可以这么做。
2、寻找K大元素
问题描述:在数组中找到第k大的元素,这种可以使用快选进行查找,总是时间复杂度是O(nlogn),与快排类似
利用快排分割的的思想,以中间位置位置元素为标杆进行划分成左右两部分,然后在进行判断第K大元素在左半侧还是右半侧,然后进行递归查找
class Solution { public: /* * param k : description of k * param nums : description of array and index 0 ~ n-1 * return: description of return */ int kthLargestElement(int k, vector<int> nums) { // write your code here //quickSelect if(nums.size() < k){ return -1; } return kthHelper(nums, 0, nums.size() - 1, k); } int kthHelper(vector<int>& nums, int start, int end, int k){ if(start == end){ return nums[start]; } int left = start; int right = end; int mid = start + (end - start)/2; int sign = nums[mid]; while(left <= right){ while(left <= right && nums[left] > sign){ left++; } while(left <= right && nums[right] < sign){ right--; } if(left <= right){ int temp = nums[left]; nums[left] = nums[right]; nums[right] = temp; left++; right--; } } if(start + k - 1 <= right){ return kthHelper(nums, start, right, k); } if(start + k - 1 >= left){ return kthHelper(nums, left, end, k - left + start); } return nums[right + 1]; } };