pat甲级1029 Median(log(n))

我被这道题卡了两天,倒不是不知道要用logn的算法,而是pat的意思是当n+m为偶数时取分割线左边的那个数为中位数,而不是取平均值。。。心态炸裂,以后只要它没提我就不取平均了。艹
一开始我想到的方法就是合并排序,可惜内存和时间都超了。而且这道题的两个数组本身就是增序排好的,显然就算用sort都是比较坏的情况了,nlogn的时间都达不到。
我想到的第二种方法就是插入,先将a集合里的数放入数组,然后找到a[i]<=b[j]&&a[i+1]>=b[j]将b[j]插在该点,然后再从i+1开始找。这个时间复杂度差不多是O(n)了,可惜,时间还是超了(我当时用c++vector做的,可能这题用容器效率就不太行)。
我这两天回顾陈越姥姥的数据结构的时候发现她在测试用例里提到了这样一点:只要是边界条件就一定会出现在测试用例里。这道题每个数组最大长度为10^5所以。。而且题目限制为200ms这么多的数据这个要求可太苛刻了。。。。
O(n)都不行,那只能logn了。。。就往二分查找上靠。。。
https://blog.csdn.net/WinstonLau/article/details/99715274这个博客里有介绍这种方法,我也是现学的。但它的介绍里面有误区,就是找不到i的情况为a数组都在合并后的数组的左半边或者右半边,而不是a,b数组隔开了没有交集区域。
这里简单介绍一下这种二分法。。。二分法一般都是找一个我们需要的数的,在这题里找什么呢。(我引用了它的一张图,希望不要来打我)
显然这里用二分法的话我们肯定要直接一点了,直接找到中间(或者附近的两个数)
在这里插入图片描述
我们可以将合并排好的这个大数组分为两半,每一瓣都来自a,b数组(上图),我们要找的这个点无非就是(n+m+1)/2(奇偶情况合并),既然在这种情况下每一瓣都源于a,b两个数组并且,a,b都是增序的。我们只要找到a数组里第一个出现在右半边的a[i],和b数组第一个出现在右边的b[j]。那么a数组里最后一个出现在左边的就是a[i-1],b数组对应的就是b[j-1]。这样无论n+m是奇是偶中位数都很容易算出。现在的问题就是怎么找这个i了。容易知道i+j = (m+n+1)/2。(此时a[i]对应左边有i个数属于a,b[j]也一样)。我们先从a的一半试起,如果要满足的话必然有左边a的最大值a[i-1] < b[j]&&b[j-1]<a[i]。这个条件就是找到的条件了。如果条件不满足,无非两种情况

  1. a[i-1] > b[j]说明我们选的这个i太大了,这个a[i-1]应该在右半边的所以此时要缩小i让a[i-1]到右边去,就在二分查找里的左半边找i
  2. b[j-1] > a[i]说明此时的a[i]太小了应该在左半边,那么此时我们再去二分查找里的右半区找这个i。
    上面这三个条件就是二分查找的判断条件了,不太理解的话看一下我上面给的网址,人家图文并茂。。。
    当然还会出现找不到i的情况,为什么呢?因为我们之前的前提条件是这个大数组的左右两边每一边都来源于两个数组,那么有没有可能一个数组只在一边呢?
    显然会出现这种情况,比如a里的数组都在左半边,或者都在右半边,此时肯定找不到i满足条件的(与b[j-1]冲突),这个时候用二分查找得出的i肯定是0或者n-1。而且二分查找的start > end此时我们就可以认为是没找到合适的i了。不过这种情况更简单。
    比如a都在左边,那么此时i = n-1(实际上有n个在左边即i+1个)
    此时若n+m为奇数,左半边:i+1个属于a,j-1个属于b(i+j = (n+m+1)/2)我是默认i+j个都在左半边。右半边第一个数为b[j-1],左半边最后一个就为max(b[j-2],a[n-1])这个数就是中位数。。。。如果是偶数的话还是这个。。(哭泣,不是取平均值,是取中间线左边那个数。。)
    a在右边的情况也类似(比上面的还简单)。
#include<stdio.h>
#include<stdlib.h>
int flag = 0;
int search(int *a,int *b,int n,int m) {
    int start = 0,end = n - 1,i = n/2,j;
    while (start <= end) {
        j = (n+m+1)/2-i;
        if (a[i-1] <= b[j] && b[j-1] <= a[i]) {
            return i;
        }
        else if (a[i -1] > b[j]) {
            end = i-1;
            i = (start+end)/2;
        }
        else if (b[j-1] > a[i]) {
            start = i+1;
            i = (start+end)/2;
        }
    }
    if (start > end) {
        flag = 1;
    }
    return i;
}
int main() {
    int n,i,m,j;
    scanf("%d",&n);
    int *a = (int *) malloc(sizeof(int)*n);
    for (i = 0;i < n;i++) {
        scanf("%d",a+i);
    }
    scanf("%d",&m);
    int *b = (int *)malloc(sizeof(int)*m);
    for (i = 0;i < m;i++) {
        scanf("%d",b+i);
    }
    if (n > m) {//在短的数组里找i不然如果两个数组长度差太大可能会导致数组越界
        int *p = b,n1 = n;
        b = a;
        a = p;
        n = m;
        m = n1;
    }
    i = search(a,b,n,m);
    j = (m+n+1)/2 - i;
        /*printf("%d %d\n",i,j);*/
    if (flag == 1) {//没找到合适的i
        if (i == n-1) {//a都在左边
            if ((n+m)%2) {
                printf("%d",b[j-2]);
            }
            else {
                int fina = b[j-2] > a[n-1]?b[j-2]:a[n-1];
                printf("%d",fina);
            }
        }
        else if (i == 0) {//a都在右边
            if ((m+n)%2) {
                printf("%d",b[j-1]);
            }
            else {
                int min = a[0]>b[j]?b[j]:a[0];
                printf("%d",b[j-1]);
            }
        }
        return 0;
    }
    if ((n+m)%2) {
        int median = a[i-1]>b[j-1]?a[i-1]:b[j-1];
        printf("%d",median);
    }
    else {
        int leftmax = a[i-1]>b[j-1]?a[i-1]:b[j-1];
        int rightmin = a[i]>b[j]?b[j]:a[i];
        printf("%d",leftmax);
    }
    return 0;
}

不知道为啥用c++vetor就算我这种方法也会超时可能需要resize()一下。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值