[剑指-Offer] 40. 最小的k个数(快速排序、海量数据、巧妙解法)

1. 题目来源

链接:最小的k个数
来源:LeetCode——《剑指-Offer》专项

2. 题目说明

输入整数数组 arr ,找出其中最小的 k 个数。例如,输入 4、5、1、6、2、7、3、8 这 8 个数字,则最小的 4 个数字是 1、2、3、4。

示例 1:

输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]

示例 2:

输入:arr = [0,1,2,1], k = 1
输出:[0]

限制:

  • 0 <= k <= arr.length <= 10000
  • 0 <= arr[i] <= 10000

3. 题目解析

方法一:快排思想+ O ( n ) O(n) O(n)时间复杂度算法+巧妙解法

其实在 [剑指-Offer] 39. 数组中出现次数超过一半的数字(多数投票算法、sort函数、代码优化)
中就可以采用快速排序中基于 partition 函数的思想来解决该问题,在原书中该方法也是大力被倡导。故在本问题中进行实现。基于数组的第 k 个数字来调整,则使得比第 k 个数字小的所有数字都位于数组的左边,比第 k 个数字打的数字都位于数组的右边。这样调整过后,位于数组中左边的 k 个数字就是最小的 k 个数字,但是这 k 不是排序的。

使用三数去中法来确定快排的基准值,双指针遍历法来进行区间划分。详情请见博主的排序算法系列博文:[排序算法] 6. 快速排序多种递归、非递归实现及性能对比(交换排序)

参见代码如下:

// 执行用时 :32 ms, 在所有 C++ 提交中击败了86.92%的用户
// 内存消耗 :21.2 MB, 在所有 C++ 提交中击败了100.00%的用户

class Solution {
public:
	int getMidIndex(vector<int> &array, int start, int end) {
		int mid = start + ((end - start) >> 1);
		int a = array[start];
		int b = array[mid];
		int c = array[end];
		if ((a >= b && a <= c) || (a <= b && a >= c)) return start;
		if ((b >= a && b <= c) || (b <= a && b >= c)) return mid;
		if ((c >= a && c <= b) || (c <= a && c >= b)) return end;
		return -1;
	}

	int partition(vector<int> &array, int start, int end) {
		int midIndex = getMidIndex(array, start, end);
		int temp = array[midIndex];
		array[midIndex] = array[end];
		array[end] = temp;
		temp = array[end];
		while (start < end) {
			while (start < end && array[start] < temp) start++;
			if (start < end) {
				array[end] = array[start];
				end--;
			}
			while (start < end && array[end] >= temp) end--;
			if (start < end) {
				array[start] = array[end];
				start++;
			}
		}
		array[start] = temp;
		return start;
	}

	vector<int> getLeastNumbers(vector<int>& nums, int k) {
		if (k == 0) return {};
		int start = 0;
		int end = (int)nums.size() - 1;
		int position = partition(nums, start, end);
		int index = k - 1;
		while (index != position) {
			if (index > position) start = position + 1;
			else end = position - 1;

			position = partition(nums, start, end);
	    }
		vector<int>rs(position + 1);
		for (int i = 0; i <= position; i++) rs[i] = nums[i];
		return rs;
	}
};

方法二: O ( l o g n ) O(logn) O(logn)时间复杂度+海量数据+巧妙解法

Top k 问题还是很适合堆排序的,并且采用堆排序也能满足海量数据的要求的。在泛化一下思想,其实堆这种数据结构还是来源于二叉树,并且 RBT 能够满足我们的要求,查删插都只用 O ( l o g n ) O(logn) O(logn) 的时间复杂度。在 STLsetmultiset 都是基于红黑树实现的。可以采用 multiset 进行实现。只要这个 k 不大于内存空间,那么就能够切分的进行海量数据的比较得出答案。

下面代码实现的也是比较详细的,没有采用 auto 关键字,尽量每一步的清楚完整,尤其注意下 set 排序传参问题,以及自定义排序问题即可。

采用该解法虽然时间复杂度不如方法一,但好处是不会对原数组进行修改,而采用方法一的快排思想,会对原数组进行修改。在编写程序前应该仔细询问数据大小是否能一次载入内存、数据允许被修改吗?切记不要一上来就撸代码,没啥意义,会留下不仔细考虑问题的坏印象。

参见代码如下:

// 执行用时 :56 ms, 在所有 C++ 提交中击败了40.30%的用户
// 内存消耗 :26.9 MB, 在所有 C++ 提交中击败了100.00%的用户

class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        vector<int> vt;
        multiset<int, greater<int>> s;
        multiset<int, greater<int>>::iterator sit;
        vector<int>::iterator vit = arr.begin();
        for (; vit != arr.end(); ++vit) {
            if (s.size() < k) s.insert(*vit);
            else {
                sit = s.begin();
                if (*vit < *(s.begin())) {
                    s.erase(sit);
                    s.insert(*vit);
                }
            }   
        }
        for (auto it = s.begin(); it != s.end(); ++it) vt.emplace_back(*it);
        return vt;
    }
};
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值