剑指Offer:面试题30 最小的k个数

/*
最小的k个数:
输入n个整数,找出其中最小的k个数。例如输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字时1、2、3、4。


分析:
解法1:可以修改输入的。只需利用划分函数,对下标为k的数字进行划分,那么最左边的k各数字一定是最小的k个
数字,但是不一定有序。因为数组的下标是从0开始的,则当iIndex = k -1时,即可


解法2:O(nlogK)的算法,适合处理海量数据(适合n很大,k很小)
先创建一个大小为k的数据容器来存储最小的k各数字,每次从输入的n个整数中读入一个数。如果容器中已有的数字
少于k个,直接把这次读入的整数放入容器中。如果容器中已经有k个数,此时只能替换原有数字。找出k个数字中
的最大值,然后依次拿待插入的整数和最大值进行比较,若待插入的小,就替换。
容器满了做3件事:
1在k个整数中找最大数
2在容器中删除最大数
3可能要插入一个新数字


必须用二叉树实现这个容器,则可以在O(logK)实现三步,对于n个数字,总效率就是O(nlogK)
而红黑树可以确保:查找、删除、插入都只需要O(logK)时间,可以用multiset来实现


对于海量数据中寻找最小的k个数字,由于不能将海量数据一次性载入,因此可用辅存来每次读入一个数字,来判定
是否需要放入容器中


输入:
每个测试案例包括2行:
第一行为2个整数n,k(1<=n,k<=200000),表示数组的长度。
第二行包含n个整数,表示这n个数,数组中的数的范围是[0,1000 000 000]。
输出:
对应每个测试案例,输出最小的k个数,并按从小到大顺序打印。
样例输入:
8 4
4 5 1 6 2 7 3 8
10 5
1 9 8 6 4 1 0 4 3 5
样例输出:
1 2 3 4
0 1 1 3 4 
*/


/*
关键:
1 利用划分函数,对下标为k的数字进行划分,那么最左边的k各数字一定是最小的k个
数字,但是不一定有序。
2 while(iIndex != k-1 )//注意,这里不是iIndex = k,因为数组的第k-1下标,表示数组中第k个数
3 O(nlogK)的算法,适合处理海量数据(适合n很大,k很小)
先创建一个大小为k的数据容器来存储最小的k各数字,每次从输入的n个整数中读入一个数。如果容器中已有的数字
少于k个,直接把这次读入的整数放入容器中。如果容器中已经有k个数,此时只能替换原有数字。找出k个数字中
的最大值,然后依次拿待插入的整数和最大值进行比较,若待插入的小,就替换。
4 vector<int>::const_iterator itV;//对于常量容器,必须要用常量迭代器
5 if(setLeastK.size() < k)//如果存储最小k个元素的容器大小<K,则直接加入
{
setLeastK.insert(*itV);
}
else//如果已经达到k个元素,则找出其中的最大元素,删除该最大元素后,再加入新的小元素
6int low = 0,high = iLen -1;//注意,这里做划分的函数的下标必须能取到
*/


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <set>
#include <vector>
#include <stack>


using namespace std;


const int MAXSIZE = 200001;
int iArr[MAXSIZE];


int randomInRange(int min,int max)
{
return (rand() % (max - min + 1) + min);
}


void swap(int* pNum1,int* pNum2)
{
int iTemp = *pNum1;
*pNum1 = *pNum2;
*pNum2 = iTemp;
}


int partition(int low,int high)
{
int iIndex = randomInRange(low,high);
swap(&iArr[low],&iArr[iIndex]);
int iAxis = iArr[low];
while(low < high)
{
while(low < high && iArr[high] >= iAxis)
{
high--;
}
iArr[low] = iArr[high];
while(low < high && iArr[low] <= iAxis)
{
low++;
}
iArr[high] = iArr[low];
}
iArr[low] = iAxis;
return low;
}


void minKNum(int iLen,int k)
{
int low = 0,high = iLen -1;//注意,这里做划分的函数的下标必须能取到
int iIndex = partition(low,high);
while(iIndex != k-1 )//注意,这里不是iIndex = k,因为数组的第k-1下标,表示数组中第k个数
{
if(iIndex > k-1 )
{
high = iIndex - 1;
}
else
{
low = iIndex + 1;
}
iIndex = partition(low,high);
}
for(int i = 0 ; i < k ; i++)
{
if(i)
{
printf(" %d",iArr[i]);
}
else
{
printf("%d",iArr[i]);
}
}
printf("\n");
}


void minKNum_BigData(const vector<int>& vecData,multiset<int,greater<int> >& setLeastK,int k)//用vector来存储数据,用multiset来存储最小的k个数,注意这里用greater<int>来确保容器中的第一个数字是最大的数字
{
vector<int>::const_iterator itV;//对于常量容器,必须要用常量迭代器
for(itV = vecData.begin() ; itV != vecData.end() ; itV++)
{
if(setLeastK.size() < k)//如果存储最小k个元素的容器大小<K,则直接加入
{
setLeastK.insert(*itV);
}
else//如果已经达到k个元素,则找出其中的最大元素,删除该最大元素后,再加入新的小元素
{
multiset<int,greater<int> >::iterator itMax = setLeastK.begin();
if(*itMax > *itV)
{
setLeastK.erase(itMax);//注意这里void erase(iterator it),删除的是迭代器不是真实的数字
setLeastK.insert(*itV);
}
}
}
multiset<int,greater<int> >::iterator itSet;
int iCnt = 0;
stack<int> stackLeastK;
for(itSet = setLeastK.begin() ; itSet !=  setLeastK.end(); itSet++)//因为打印的时候需从小到大打印,这里费劲了,用栈
{
if(iCnt == k)
{
break;
}
stackLeastK.push(*itSet);
}
bool isFirst = true;
while(!stackLeastK.empty())
{
if(!isFirst)
{
printf(" %d",stackLeastK.top());
}
else
{
isFirst = false;
printf("%d",stackLeastK.top());
}
stackLeastK.pop();
}
printf("\n");
}


void process()
{
int n,k;
while(EOF != scanf("%d %d",&n,&k))
{
if(n < 1 || n >= MAXSIZE || k < 1 || k >= MAXSIZE)
{
continue;
}
memset(iArr,0,sizeof(iArr));
for(int i = 0 ; i < n ; i++)
{
scanf("%d",&iArr[i]);
}
vector<int> vecData(iArr,iArr+n);
multiset<int,greater<int> > setLeastK;
minKNum(n,k);
minKNum_BigData(vecData,setLeastK,k);
}
}


int main(int argc,char* argv[])
{
process();
getchar();
return 0;
}


 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值