题目:传送门
**题意:**这个题题意很好理解,就是给了两个正序数组 nums1 和 nums2 ,要求得出两个正序数组的中位数,最简单的思路就是把两个数组合并,然后就可以简单的求的中位数,但是这样就会浪费时间和空间,首先你要开一个大的数组用来存数据,然后你存数据的时候要遍历两个数组,这个时候的时间复杂度就是O(m+n)。那有没有其他办法呢?我的思路是定义两个指针,分别指向nums1 和nums2,因为两个数组给的是正序,那么我就只需要移动指针将前 (m+n-1)/2个数据移出去,这个时候指针指向的数据就是我们需要的中位数。
如果这样有没有什么特殊情况要考虑呢?有,我让 x=(m+n-1)/2,用x来控制循环,那么如果x==0,也就是我现在的指针已经指向了我需要的数据,那么如果一开始x就等于0呢,是由这种情况的,例如nums1=[],nums2=[1]、nums1=[2],nums2=[1]、nums1=[],nums2=[1,2],例如这三中情况,这个时候x=0,为什么把这种情况单独拿出来呢,因为我使用了指针,这时候我必须考虑指针是否越界问题,因为在移动指针的时候,很有可能把指针移出到数组外,造成数组越界的问题。所以这个思路的问题,也就是最需要注意的点,考虑指针是否越界,一定要考虑。
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int flag=(nums1.size()+nums2.size())%2;
int x=(nums1.size()+nums2.size()-1)/2;
int len1=0,len2=0;
double ans=0;
if(x==0){
if(nums1.size()==2&&nums2.size()==0){
ans=1.0*(nums1[0]+nums1[1])/2;
return ans;
}
else if(nums1.size()==1&&nums2.size()==1){
return 1.0*(nums2[0]+nums1[0])/2;
}
else if(nums1.size()==1&&nums2.size()==0){
return 1.0*nums1[0];
}
else if(nums2.size()==2)
{
return 1.0*(nums2[0]+nums2[1])/2;
}
else {
return 1.0*nums2[0];
}
}
while(x){
if(len1>=nums1.size()){
len2++;
x--;
continue;
}
if(len2>=nums2.size()){
len1++;
x--;
continue;
}
if(nums1[len1]>=nums2[len2]){
len2++;
}else{
len1++;
}
x--;
}
if(len1>=nums1.size()){
ans+=nums2[len2];
len2++;
}
if(len2>=nums2.size()){
ans+=nums1[len1];
len1++;
}
if(len1<nums1.size()&&len2<nums2.size()){
if(nums1[len1]>=nums2[len2]){
ans+=nums2[len2];
len2++;
}else{
ans+=nums1[len1];
len1++;
}
}
if(!flag){
if(len1>=nums1.size()){
ans+=nums2[len2];
ans/=2;
}
if(len2>=nums2.size()){
ans+=nums1[len1];
ans/=2;
}
if(len1<nums1.size()&&len2<nums2.size()){
if(nums1[len1]>=nums2[len2]){
ans+=nums2[len2];
ans/=2;
}else{
ans+=nums1[len1];
ans/=2;
}
}
}
return ans;
}
};
反思:代码写的长乱,思考问题不全面,我是菜鸡。
这个问题还有进阶,看到log,我们应该想到的应该是二分查找,那么该如何使用二分呢?就像上面我们那个思路,我们把通过移动指针把两个数组的一半给切割,那如果是使用二分呢?
首先什么是二分查找,很简单,就是使用一个数组中间的值,我们给中间的值进行比较,从而切割数组,把数组分成一半,下次查找就从新的区间开始重复,这就是简单的二分查找的思路,那这个题呢?该如何应用二分呢?
首先,我们要知道,这个题的要求,我们要找到两个点,这两个点需要把两个数组切割,使得两个数组的左边正好是我们需要丢弃的数值,那么我们该如何使用二分的思路来寻找这两个切割的点呢?我们规定切割的两个点的下标是i和j,那么这两个点需要满足的条件是:nums1[i-1]<=nums2[j]&&nums2[j-1]<=nums1[i],如果这个条件满足,说明i和j左边的值都是在nums1[i]和nums2[j]的左边,如果两边的个数正好是两个数组和的一半,那么我们就求得了切割点。那么我们该如何和二分练习起来呢?我们可以先取一个短数组的中间点,为什么用短数组,因为这样可以使遍历的次数少,这样我们首先得到一个点i,那么我们该如何得到j呢,还有一个条件,那就是我希望i和j的左边的数值正好是两个数组和的一半。
int i=(left+right)/2;
int j=(n+m+1)/2-i;//i和j记录nums1和nums2的切割点
现在我们得到了切割点,现在我们就应该判断是否满足情况。
if(lnum_i<num_j)
大家会发现,这样判断就只满足了一半的条件,那就是满足了nums1[i-1]<=nums2[j]这个条件,还有一半条件没有满足啊,但是大家要知道我们使用二分是递归查找的,所以我们不需要一下子就满足所有情况,但是我们可以使用递归来使所有情况的判别到,我们知道两个数组都是正序数组,那么这里面就含有了很多其他条件,就像如果nums1[i-1]<=nums2[j]为真,那么nums2[j]就大于i-1左边全部的数值,如果不成立,我们就需要继续向i-1左边的数组继续切割,这样就满足了二分的思想。
这就是二分的本质:
if(lnum_i<num_j){
lmedin=max(lnum_i,lnum_j);
rmedin=min(num_i,num_j);
left=i+1;
}else{
right=i-1;
}
我们上完整代码:
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
if(nums1.size()>nums2.size()){
return findMedianSortedArrays(nums2,nums1);//需要将短的数组放到nums1
}
int m=nums1.size(),n=nums2.size();
int lmedin=0,rmedin=0;
int left=0,right=m;
while(left<=right){
int i=(left+right)/2;
int j=(n+m+1)/2-i;//i和j记录nums1和nums2的切割点
int lnum_i= (i==0)?INT_MIN:nums1[i-1];
int num_i=(i==m)?INT_MAX:nums1[i];
int lnum_j=(j==0)?INT_MIN:nums2[j-1];
int num_j=(j==n)?INT_MAX:nums2[j];
if(lnum_i<num_j){
lmedin=max(lnum_i,lnum_j);
rmedin=min(num_i,num_j);
left=i+1;
}else{
right=i-1;
}
}
return ((m+n)%2==0)?(lmedin+rmedin*1.0)/2:lmedin*1.0;
}
};
这个题我认为非常好,对二分的应用有了更好的理解,可能我说的并不是很通俗易懂,希望大家可以自己写一下这个题,如果没有思路,可以看一下我的代码好好理解理解,然后自己好好思考,自己敲一下这个题。
然后总结一下,我感觉写这种题最重要的就是找状态机制,认真分析,找准状态机制,如果找到就很简单了,不止这种题,感觉所有题都是这样,然后,希望大家加油。