Rust面试宝典第9题:找出第K大元素

87 阅读5分钟

题目

给定一个整数数组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)。

💡 如果想阅读最新的文章,或者有技术问题需要交流和沟通,可搜索并关注小红书“希望睿智”。