leetcode 4.Median of two sorted arrays
leetcode 4.Median of two sorted arrays
解法1:双指针合并序列(O(m+n))
最朴素的做法,可以做一点点小小的优化:
我们不需要记录合并后的数组,只需要用一个变量记录当前的下标位置即可,执行到中间时直接return即可。
但是因为太久没有写算法题,在一些边界问题上还是浪费了一些时间。
class Solution { public: double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) { int lens = nums1.size() + nums2.size(); int end_point = lens/2 + 1; int ans = 0; int t1 = INT_MAX, t2 = INT_MAX; // now_count已添加的个数 for(int i = 0, j = 0, now_count = 0; now_count < end_point;) { if(i < nums1.size() && j < nums2.size()) { if(nums1[i] <= nums2[j]) t1 = nums1[i++]; else t1 = nums2[j++]; } else { if(i < nums1.size()) t1 = nums1[i++]; else t1 = nums2[j++]; } now_count++; if(now_count == end_point - 1 && now_count > 0) { ans = ans + t1; t2 = t1; } else if(now_count == end_point) ans = ans + t1; } if((end_point << 1) - lens == 1) { if(t2 != INT_MAX) ans = ans - t2; return ans; } else return (double)ans/2; } };
避免数组越界
这里要注意,nums1和nums2都有可能为空,所以在判断nums1[i]和nums2[j]取哪个的时候,不能直接这样写:
if(i < nums1.size() && nums1[i] <= nums2[j]) { t1 = nums1[i]; i++; } else if(j < nums2.size()) { t1 = nums2[j]; j++; }
当nums2为空的时候,这里就会越界报错了。
小心只需要取一个数的情况
if(now_count == end_point - 1 && now_count > 0) { ans = ans + t1; t2 = t1; }
if((end_point << 1) - lens == 1) { if(t2 != INT_MAX) ans = ans - t2; return ans; }
这两处特判都是为了处理只需要取一个数的情况。
解法2:正常二分(O(log(min(m,n))))
这种二分做法比题目给出的目标复杂度还要快,说实话,我反而想不到O(log(m+n))要怎么做,因为两个序列如果都单独来看,都是具有单调性、可二分的,但如果放在一起的话这种单调性就消失了。
class Solution { public: double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) { // 保证nums1是较短的数组 if(nums1.size() > nums2.size()) return findMedianSortedArrays(nums2, nums1); int m = nums1.size(), n = nums2.size(); int l = -1, r = m - 1; int cnt = (m + n + 1)/2; // cnt是个数, 向上取整 int nums1_less, nums1_more, nums2_less, nums2_more; while(l <= r) { int mid = l + r >> 1, mid2 = cnt - mid - 2; // mid是下标,cnt - (mid + 1)算出来是个数,再减一转换成下标mid2 nums1_less = (mid < 0) ? INT_MIN : nums1[mid]; nums1_more = (mid + 1 < m) ? nums1[mid + 1] : INT_MAX; nums2_less = (mid2 < 0 || !n) ? INT_MIN : nums2[mid2]; nums2_more = (mid2 + 1 < n) ? nums2[mid2 + 1] : INT_MAX; // cout << mid << " " << mid2 << endl; if(nums1_less <= nums2_more && nums1_more >= nums2_less) { // cout << mid << " " << mid2 << endl; if(m + n & 1) // 奇数 return max(nums1_less, nums2_less); else return ((double)max(nums1_less, nums2_less) + min(nums1_more, nums2_more)) / 2; } else if(nums1_less < nums2_less) l = mid + 1; else r = mid - 1; } return 233333; } };
二分的具体策略是二分答案,在较短的数组中选一个位置,假定中位数就在这里,然后去验证条件是否满足。
平时做的二分都是在分割点右侧所有点都满足答案条件,左边全都不满足;
但是这道题比较特殊,这道题是只有一个点满足答案,左右两边都不满足,所以循环结束的条件是l<=r;而且一旦找到满足条件的点就要结束循环。
因为nums1可能一个都不选,所以l要设置为一个越界值,即-1。
解法三:伪二分
// 伪二分做法 class Solution { public: double findMedianSortedArrays(std::vector<int>& nums1, std::vector<int>& nums2) { int m = nums1.size(), n =nums2.size(); if(m > n) return findMedianSortedArrays(nums2, nums1); int total_nums = m + n; if(total_nums & 1) return find_kth_in_array(nums1, nums2, total_nums/2 + 1); else return (find_kth_in_array(nums1, nums2, total_nums/2) + find_kth_in_array(nums1, nums2, total_nums/2 + 1)) / 2; } double find_kth_in_array(std::vector<int>& nums1, std::vector<int>& nums2, int k) { int idx1 = 0, idx2 = 0; int m = nums1.size(), n = nums2.size(); while(true) { if(idx1 >= m) return nums2[idx2 + k - 1]; if(idx2 >= n) return nums1[idx1 + k - 1]; if(k == 1) { return std::min(nums1[idx1], nums2[idx2]); } int k1 = k/2, k2 = k - k1; int new_idx1 = std::min(m - 1, idx1 + k1 - 1), new_idx2 = std::min(n - 1, idx2 + k2 - 1); int p1 = nums1[new_idx1], p2 = nums2[new_idx2]; if(p1 <= p2) { k -= new_idx1 - idx1 + 1; idx1 = new_idx1 + 1; } else { k -= new_idx2 - idx2 + 1; idx2 = new_idx2 + 1; } } } };
注意特判要把k==1的情况放在最后,因为nums1有可能是空的,如果把k==1的情况放在第一个,就可能出现越界的情况。
这个做法的核心逻辑是,如果p1<=p2,那么p1以及它左边的数都太小了,不可能成为最后的答案,就把他们都舍弃掉,类似于二分答案的思想,而且更新k的过程这个操作也类似于二分,k衰减的速度和传统二分是差不多的。