找到一个无序数组中第K小的数。
在线评测地址:LintCode 领扣
样例 1:
输入: [3, 4, 1, 2, 5], k = 3
输出: 3
样例 2:
输入: [1, 1, 1], k = 2
输出: 1
【题解】
最容易想到的就是直接排序,返回第k大的值。时间复杂度是O(nlogn)
,这里提供O(n)
的解法。
这题其实是快速排序算法的变体,通过快速排序算法的partition
步骤,可以将小于pivot
的值划分到pivot
左边,大于pivot
的值划分到pivot
右边,所以可以直接得到pivot
的rank
。从而缩小范围继续找第k
大的值。
partition
步骤:
- 令
left = start
,right = end
,pivot = nums[left]
。 - 当
nums[left] < pivot
时,left
指针向右移动。 - 当
nums[right] > pivot
时,right
指针向左移动。 - 交换两个位置的值,
right
指针左移,left
指针右移。 - 直到两指针相遇,否则回到第
2
步。
每次partition
后根据pivot
的位置,寻找下一个搜索的范围。
复杂度分析
设数组长度为n
时间复杂度O(n)
- 对一个数组进行
partition
的时间复杂度为O(n)
。 - 分治,选择一边继续进行
partition
。 - 所以总的复杂度为
T(n) = T(n / 2) + O(n)
,总时间复杂度依然为O(n)
。
空间复杂度O(1)
- 只需要快速选择游标的
O(1)
额外空间。
public class Solution {
/**
* @param k: An integer
* @param nums: An integer array
* @return: kth smallest element
*/
public int kthSmallest(int k, int[] nums) {
int n = nums.length;
// 数组从0开始标号,要传k - 1
return partition(nums, 0, n - 1, k - 1);
}
private int partition(int[] nums, int start, int end, int k) {
int left = start, right = end;
int pivot = nums[left];
while (left <= right) {
while (left <= right && nums[left] < pivot) {
left++;
}
while (left <= right && nums[right] > pivot) {
right--;
}
if (left <= right) {
swap(nums, left, right);
left++;
right--;
}
}
// 如果第 k 小在右侧,搜索右边的范围,否则搜索左侧。
if (k <= right) {
return partition(nums, start, right, k);
}
if (k >= left) {
return partition(nums, left, end, k);
}
return nums[k];
}
private void swap(int[] nums, int x, int y) {
int temp = nums[x];
nums[x] = nums[y];
nums[y] = temp;
}
}
更多题解参见:九章算法