题目再现
题目内容:
给定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