1 题目
题目:无序数组K小元素(Kth Smallest Numbers in Unsorted Array)
描述:找到一个无序数组中第 K 小的数(K 从1开始)。
lintcode题号——461,难度——medium
样例1:
输入: [3, 4, 1, 2, 5], k = 3
输出: 3
样例2:
输入: [1, 1, 1], k = 2
输出: 1
2 解决方案
2.1 思路
可以通过排序找到第k个数,排序的时间复杂度为O(n * log n)。该题还有更优的解法(quick select算法),可以通过快速排序的思路,但是只对数组做一半的快速排序,因为我们只需要关心存在结果的那一半部分的数组的排序就能够得到第k个数的位置。
2.2 时间复杂度
每次找到并抛掉数组中不存在结果的区间的操作耗时为O(n),即在O(n)的耗时内,将T(n)规模的问题变为T(n/2)规模。
T(n) = T(n/2) + O(n)
= T(n/4) + O(n/2) + O(n)
= T(n/n) + O(2 + …… + n/4 + n/2 + n)
= T(1) + O(2n)
= O(n)
所以总时间复杂度依然为O(n)。
2.3 空间复杂度
空间复杂度为O(1)。
3 源码
细节:
- 使用quick select算法,快速排序的只递归一半,循环条件为left<=right。
- 第k小的数,即有序后的下标第k-1的数。
注意区别quick select与对向双指针,双指针只要left<right即可,quick select和快排由于要递归,令循环结束后left越过right,需要多走一步,把left、right作为边界递归子区间start-right和left-end,所以需要left<=right。
C++版本:
/**
* @param k: An integer
* @param nums: An integer array
* @return: kth smallest element
*/
int kthSmallest(int k, vector<int> &nums) {
// write your code here
int result = 0;
if (nums.empty())
{
return result;
}
result = quickSelect(nums, 0, nums.size() - 1, k - 1); // 第k小的数,即下标第k-1的数
return result;
}
int quickSelect(vector<int> & nums, int start, int end, int k)
{
// 出口
if (start == end)
{
return nums.at(start);
}
int left = start;
int right = end;
int key = nums.at(left + (right - left) / 2); // 任意寻找一个位置为key
while (left <= right) // 因为要对子数组递归,所以要取等号,令循环结束后left越过right
{
if (nums.at(left) < key) // 使用while需要添加判断条件left<=right,if则不用
{
left++;
continue;
}
if (nums.at(right) > key)
{
right--;
continue;
}
if (left <= right)
{
swap(nums.at(left++), nums.at(right--));
}
}
if (left <= k && left <= end) // 注意left不能越界,k和end都大于left
{
return quickSelect(nums, left, end, k);
}
else if (k <= right && start <= right) // 注意right不能越界,k和start都小于right
{
return quickSelect(nums, start, right, k);
}
else
{
return nums.at(k);
}
}