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