题目
给定一个整数数组a,同时给定它的大小N和要找的K(1 <= K <= N),请根据快速排序的思路,找出数组中第K大的数(保证答案存在)。比如:数组a为[50, 23, 66, 18, 72],数组大小N为5,K为3,则第K大的数为50。
解析
这道题主要考察应聘者对于快速排序的理解,以及实际运用的能力。快速排序是一种高效的排序算法,采用分治策略进行排序。以下是快速排序的具体步骤:
选择轴心(pivot):首先,从待排序的数组中选择一个元素作为轴心。选择轴心的方式有多种,可以选择第一个元素、最后一个元素、中间元素,或者随机选择一个元素。
划分(Partition):重新排列数组,使得所有比轴心小的元素都排在轴心的左边,所有比轴心大的元素都排在轴心的右边。在这个过程中,轴心的位置也确定了。
递归排序子数组:递归地对轴心左边和右边的两个子数组进行快速排序。递归的终止条件是:子数组的长度为1或0,此时子数组已经有序。
根据上面的分析,我们可以写出快速排序的示例代码。
fn partition(arr: &mut [i32], low: usize, high: usize) -> usize {
let pivot = arr[low];
let mut i = low;
let mut j = high;
while i < j {
// 从右向左开始找一个小于等于nPivot的数
while i < j && arr[j] > pivot {
j -= 1;
}
if i < j {
arr[i] = arr[j];
i += 1;
}
// 从左向右开始找一个大于nPivot的数
while i < j && arr[i] <= pivot {
i += 1;
}
if i < j {
arr[j] = arr[i];
j -= 1;
}
}
arr[i] = pivot;
i
}
fn quick_sort(arr: &mut [i32], low: usize, high: usize) {
if low < high {
// 返回轴心元素的位置
let pivot = partition(arr, low, high);
if pivot > 0 {
// 在左区间递归进行快速排序
quick_sort(arr, low, pivot - 1);
}
// 在右边区间递归进行快速排序
quick_sort(arr, pivot + 1, high);
}
}
fn main() {
let mut nums = vec![5, 2, 9, 1, 5, 6, 8, 7, 3];
let high = nums.len() - 1;
quick_sort(&mut nums, 0, high);
println!("{:?}", nums);
}
回到我们的题目,如何利用快速排序算法,找到数组中第K大的元素呢?根据快速排序算法的原理,我们知道,执行一次划分后,轴心元素的位置是保持不变的。我们需要找第K大的元素,也就是第(N-K)小(序号从0开始计算)的元素。如果划分后返回的轴心元素的索引正好等于(N-K),则说明不需要继续找了,轴心元素就是我们需要的第K大元素。如果划分后返回的轴心元素的索引大于(N-K),则说明第K大元素在左区间,需要在左区间继续递归查找。同样的,如果划分后返回的轴心元素的索引小于(N-K),则说明第K大元素在右区间,需要在右区间继续递归查找。
根据上面的分析,我们可以写出相关的示例代码,具体如下。
fn partition(nums: &mut [i32], low: usize, high: usize) -> usize {
let pivot = nums[low];
let mut i = low;
let mut j = high;
while i < j {
while i < j && nums[j] <= pivot {
j -= 1;
}
nums[i] = nums[j];
while i < j && nums[i] > pivot {
i += 1;
}
nums[j] = nums[i];
}
nums[i] = pivot;
i
}
fn quick_select(nums: &mut [i32], low: usize, high: usize, kth_smallest: usize) -> usize {
if low == high {
return low;
}
let pivot_index = partition(nums, low, high);
if pivot_index == kth_smallest {
return pivot_index;
} else if pivot_index > kth_smallest {
quick_select(nums, low, pivot_index - 1, kth_smallest)
} else {
quick_select(nums, pivot_index + 1, high, kth_smallest)
}
}
fn find_kth_largest(nums: &mut [i32], kth_largest: usize) -> usize {
let len = nums.len();
quick_select(nums, 0, len - 1, len - kth_largest)
}
fn main() {
let mut nums = vec![32, 63, 15, 46, 68, 8, 79, 12, 5, 72];
println!("{:?}", nums);
let kth_largest = 3;
let index = find_kth_largest(&mut nums, kth_largest);
println!("The {} largest number is: {}", kth_largest, nums[index]);
}
在上面的示例代码中,partition函数用于将数组划分为两部分,并返回轴心元素的索引。quick_select函数通过递归调用,在左半部分或右半部分查找第kth_smallest小的元素。然后,find_kth_largest函数调用quick_select函数来查找第kth_largest大的元素,并返回其索引。最后,在main函数中,我们自定义了输入的数组和要查找的K值,并输出了结果。
总结
通过这道题,我们学习了快速选择算法。快速选择算法是一种基于快速排序的算法,用于在未排序的数组中找到第K大或者第K小的元素。它的平均时间复杂度为O(n),最坏情况下的时间复杂度为O(n^2)。与快速排序算法类似,快速选择算法是一种原地算法,不需要额外的存储空间。
学习了上面的示例代码后,你真的理解快速选择算法了吗?我们为你留了一些课后的拓展作业,快来试一试吧!
1、给定一个无序的整数数组和一个目标整数值,求出数组中比目标整数值小的元素个数,要求时间复杂度为O(n)。
💡 如果想阅读最新的文章,或者有技术问题需要交流和沟通,可搜索并关注小红书“希望睿智”。