Find the Kth number(找第K大数)


题目再现

题目内容:
给定N个排序好的序列,每个序列内有M个数字。因此我们总共有N*M个数字,编号为1~N*M。
将N*M个数字排序后输出第K个数字是多少。

Hint : 直接将N*M个数字做排序会超过时间限制。
Hint : 每次花O(N)的时间找一个数字,一直找到第K个数字也会超过时间限制。
Hint : 使用一个大小为N的heap,记录每个序列目前最小的数字是多少,以及这是该序列的第几个数字。
Hint : 不要将N*M个数字都产生出来,会超过memory限制。

输入格式:
只有一笔测资。
    第一行有三个数字N M K,意义如题目所述。
    接下来的N行,每行有2个数字a b,代表一个f(x) = ax+b。这一行的序列即为[f(1), f(2), … , f(M)]。你可以假设每一行的f(x)都会是一个递增函数,使得你的序列肯定是排序好的序列(由小到大)。

数字范围:
0 < N <= 17000
0 < M <= 10000

0 <= a,b <= 100

输出格式:
输出一行数字,第K个数字是多少。

输入样例:
3 3 7
1 3
2 2
3 1

输出样例:
7
时间限制:500ms内存限制:128000kb

问题解决

解法一

解决这种第K大的问题,想到的第一点就是用堆排序的方式,堆的大小为K,如果要找第K小的数,就建立最大堆;如果要找第K大的数,就要建立最小堆。所以我的解法一就是建立一个K大的最大堆,堆顶就是要求的第K小的数。

#include <stdio.h>

int arrHeap[1000000];
int n = 0; // heap position

void swap(x, y){  
    int t;  

    t = arrHeap[x];  
    arrHeap[x] = arrHeap[y];  
    arrHeap[y] = t;  
}  

void shiftDown(int x){  
    int t, flag = 0;  

    while(x * 2 <= n && flag == 0){  
        if(arrHeap[x * 2] > arrHeap[x]){  
            t = x * 2;  
        }else{  
            t = x;  
        }  

        if(x * 2 + 1 <= n){  
            if(arrHeap[x * 2 + 1] > arrHeap[t]){  
                t = x * 2 + 1;  
            }  
        }   

        if(t == x){  
            flag = 1;  
        }else{  
            swap(x, t);  
            x = t;  
        }  
    }  
} 

void creatHeap(){  
    int i;  
    for(i = n / 2; i >= 1; i--){  
        shiftDown(i);  
    }  
}  

int main(){
    int N, M, K;
    int a, b, i, tmp; 

    scanf("%d %d %d", &N, &M, &K);

    while(N --){
        scanf("%d %d", &a, &b);

        for(i = 1; i <= M; i ++){
            tmp = a * i + b;
            if(n <= K - 1){
                arrHeap[++ n] = tmp;
                if( n == K){
                    creatHeap();
                }
            }else if(tmp < arrHeap[1]){
                arrHeap[1] = tmp;
                shiftDown(1);
            }
        }
    }

    printf("%d", arrHeap[1]);

    return 0;
}

算法是没问题的,对于测试样例也是正确的,但是题目有时间限制,而要计算的数值又很大,我的算法是O(MN),所以超时 没有通过。

解法二

其实,题目中说过一点,给出N个M长度的序列,而且每个序列都是按照升序的方式排列好的,所以为了降低时间复杂度,可以不用堆排序,而用优先队列的方式进行,然后出队K - 1个,那么第K个数就是队头了,那么怎么建立这个优先队列呢?应该怎样进行存储呢?其实这就是问题的关键,根据题意,我们只需要按列的方式进行入队即可轻松完成,时间复杂度是O(NlogN)。

#include <stdio.h>
#include <stdlib.h>

//用于存储N * M 个结点类型 
struct Item{
    int value;
    int indexRow;
    int indexCol;
};

//优先队列(没办法C语言里面没有C++那样的STL,只能自已去写这个) 
struct Queen{
    struct Item value;
    struct Queen *next;
}; 

struct Item qTop(struct Queen **head){
    return (*head)->value;
}

void qPop(struct Queen **head){
    struct Queen *qTemp;

    if((*head) != NULL){
        qTemp = *head;
        (*head) = (*head)->next;

        free(qTemp);        
    }
}

void qPush(struct Queen **head, struct Item x){
    struct Queen *newNode, *qHead, *curr;

    curr = qHead = *head;

    newNode = (struct Queen *)malloc(sizeof(struct Queen));
    newNode->value = x;

    if(qHead == NULL || qHead->value.value > newNode->value.value){
        newNode->next = qHead;
        qHead = newNode;
    }else{
        while(1){
            if(curr->next == NULL || curr->next->value.value > newNode->value.value){
                newNode->next = curr->next;
                curr->next = newNode;
                break;
            }else{
                curr = curr->next;
            }
        }
    }

    *head = qHead;
}

int main(){
    struct Queen *qHead = NULL;
    int N, M, K; 
    int i, j;
    struct Item tmpItem;

    scanf("%d %d %d", &N, &M, &K);  

    //给输入的a, b建表
    int arrAB[N][2];    
    for(i = 0; i < N; i ++){
        scanf("%d %d", &arrAB[i][0], &arrAB[i][1]);
    } 

    //初始化队列
    for(i = 0; i < N; i ++){
        tmpItem.value = arrAB[i][0] + arrAB[i][1];
        tmpItem.indexRow = i;
        tmpItem.indexCol = 1;

        qPush(&qHead, tmpItem);
    }

    //执行 K-1 次出队,在这个过程中不断添加下一个结点 
    for(i = 0; i < K - 1; i ++){
        tmpItem = qTop(&qHead);
        qPop(&qHead);

        if(tmpItem.indexCol + 1 <= M){ //防止K值过大超出范围 (可不加) 
            tmpItem.indexCol ++;
            tmpItem.value =  arrAB[tmpItem.indexRow][0] * tmpItem.indexCol + arrAB[tmpItem.indexRow][1];
            qPush(&qHead, tmpItem);
        }
    } 

    //OK,第K小个数找到
    printf("%d", qTop(&qHead).value); 

    return 0;
}

因为优先队列的操作是我个人写的,所以要比C++ STL中的优先队列效率要差一点,C++用户可以直接使用STL操作,C用户如果想自己实现也可参考我写的。

博客名称:王乐平博客
博客地址:http://blog.lepingde.com
CSDN博客地址:http://blog.csdn.net/lecepin


这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值