输入n个整数,找出其中最小的k个数。例如输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
这道题最简单的思路莫过于把输入的n个整数排序,排序之后位于最前面的k个数就是最小的k个数。这种思路的时间复杂度是O( nlogn),显然不太能让面试官满意.
我们可以按照找一个最小值为基础展开思路, 找到一个最小值, 然后修改原数组去掉此元素, 在找最小值, 找k次就完成了.O(kn)的时间复杂度. 是不是有点像选择排序法 , 如果k接近于n, 就会退化成选择排序, O(n^2)的时间复杂度.
- (void)viewDidLoad {
[super viewDidLoad];
[self findMinKNumSelect];
}
// 查找数组中最小的k个数字, 使用选择排序法解决
- (void)findMinKNumSelect {
NSArray * array = @[@(1),@(7),@(22),@(9),
@(4),@(5),@(122),@(8),
@(35),@(18),@(3),@(13),
@(5),@(218),@(63),@(103),
];
int a[100] = {};
for (int i = 0; i<array.count; i++) {
a[i] = [array[i] intValue];
}
// 设置查找最小的数字
int k = 6;
// --------------算法开始
NSMutableArray * result = [NSMutableArray arrayWithCapacity:k];
while (result.count<k) {
int min = a[0];
int minIndex = 0;
for (int i = 0; i<array.count-result.count; i++) {
if (a[i]<min) {
min = a[i];
minIndex = i;
}
}
NSLog(@"本次最小值 %d 下标%d",min,minIndex);
// 每次找到最小值, 把对应最小值的换成原始数组的最后一个元素
a[minIndex] = a[array.count-result.count-1];
[result addObject:@(min)];
}
NSLog(@"%@",[result componentsJoinedByString:@" "]);
}
有了选择排序法的思路, 我们在往快排上迁移一下, 看看有没有更好的思路, "快速排序法"中重要的一步就是查找基准数, 使基准数左边都比基准数小, 基准数右边都比基准数大, 如果能找到这个数字, 使得左边的数字为k个(或者k-1个,加上基准数本身就是k个),那么左边的数字就是最小的k个数字了.
找到以前写的快排代码, 去掉递归调用, 增加一个返回值,返回本次调用使用的基准值下标, 如果 (基准值下标==k || 基准值下标==k-1),说明0~k-1已经是最小的k个数字了.
- (void)viewDidLoad {
[super viewDidLoad];
[self findMinKNum];
}
// 查找数组中最小的k个数字
- (void)findMinKNum {
NSArray * array = @[@(1),@(7),@(22),@(9),
@(4),@(5),@(122),@(8),
@(35),@(18),@(3),@(13),
@(5),@(218),@(63),@(103),
];
int a[100] = {};
for (int i = 0; i<array.count; i++) {
a[i] = [array[i] intValue];
}
// 设置查找最小的数字
int k = 6;
// -------------- 算法开始
int mid = quickSort(a, 0, (int)array.count-1);
// mid==k, 那么0~k-1就是前k个最小的数
// mid==k-1, 还是0~k-1
while (mid!=k-1 && mid!=k) {
if (mid>k) {
mid = quickSort(a, 0, mid-1);
}
if (mid<k){
mid = quickSort(a, mid+1, (int)array.count-1);
}
}
NSString * str = @"";
for (int i = 0; i<k; i++) {
str = [str stringByAppendingFormat:@"%d ",a[i]];
}
NSLog(@"前k=%d个最小值是 : %@",k,str);
str = @"";
for (int i = 0; i<array.count; i++) {
str = [str stringByAppendingFormat:@"%d ",a[i]];
}
NSLog(@"完整数组为 : %@",str);
}
// 对快排算法简单改造了一下, 去掉了递归调用, 返回本次的基准值下标
int quickSort(int a[], int left, int right) {
if (a==NULL) {
return -1;
}
if (left>right) {
return -1;
}
int low = left;
int high = right;
// 选择基准数
int temp = a[left];
while (low<high) {
while (low<high && a[high]>=temp) {
high--;
}
while (low<high && a[low] <= temp) {
low++;
}
if (low<high) {
int t = a[low];
a[low] = a[high];
a[high] = t;
}
}
// 到这里说明low==high, 交换基准数和相遇点的位置
a[left] = a[low];
a[low] = temp;
NSLog(@"本次基准值下标为 %d %d",low,high);
return low;
}
如果是让找最大的k个数字, 有2种改法,
一种是修改排序函数,变成降序排列,这样前k个就是最大的k个元素,判断条件就保持原状
一种是修改判断条件. 数组还是按照升序排列, 判断条件改成这样
在想一下,如果想要找到数组中最小(或者最小)的数字, 我们一般会用一个临时变量, 然后遍历一遍数组, 每个元素依次与这个临时变量比较, 如果小于这个临时变量, 就把值赋给临时变量.
现在用一个临时变量保存肯定是不够了, 需要使用一个集合来保存这K个元素, 然后遍历数组, 集合中没有填满的时候, 把数组元素填入集合, 如果集合满了, 就用数组中元素与集合中的最大值比较, 如果比集合最大值小, 就把集合中的最大值剔除,并把此元素放入集合. 当遍历完所有元素的时候, 集合中的元素就是最小的k个数字了.