如何在无序数组中查找第n小的值

如题:给定一个无序数组,如何查找第K小的值。

例子如下:

在一个无序数组,查找 k = 3 小的数

输入:arr[] = {7, 10, 4, 3, 20, 15}

输出:7

在一个无序数组,查找 k = 4 小的数

输入:arr[] = {7, 10, 4, 3, 20, 15}

输出:10

几种思路如下和复杂度分析如下:

(1)最简单的思路直接使用快排,堆排或者归并排,排序之后取数组的k-1索引的值即可,时间复杂度为O(nLogn)

(2)用大小为k的数组存前k个数,然后找出这里面最大的值kmax,耗时O(K), 遍历剩余的数,如果有小于里面最大的数,就放进去替换掉当前最大的,依次遍历至结束,每次比较前都得找出kmax,故总的时间复杂度为:O(NK)

(3)使用大顶堆,初始化为k个值,然后后面从k+1开始,依次读取每个值,判断当前的值是否比堆顶的值小,如果小就移除堆顶的值,新增这个小的值,依次处理完整个数组,取堆顶的值就得到第k小的值。 时间复杂度为:建堆的时间为O(K),每次调整最大堆结构时间为O(lgK),从而总的时间复杂度为O(K + (N-K)lgK)(适合大数据量)

(4)利用快排找基准的原理,可以在平均时间复杂度O(N)级别完成,当然最坏的情况下是O(n2)与快排的最坏情况一样,但由于平均是O(N)的时间复杂度,所以这种方式一般认为是最优的解法。

 原理如下: 根据题目描述,如果是第k小的值,那就说明在升序排序后,这个值一定在数组的k-1的下标处,如果在k-1处,也就是说只要找到像这样的左边有k个数比k小(可以是无序的,只要小就可以了),那么这个下标的值,就是我们要找的值,利用这个思想我们就可以使用快排的思想,来快速的找基准值的index(数组下标从0开始),如果恰好碰到了基准值的下标index+1=k,那就说明基准值index所在下标的值,就是我们要找的结果。

下面的代码就是基于第四种思路来实现的,其他的方式,有兴趣的可以自己研究一下。

注意,如果思路理解了,那么该题目的变形也比较容易处理,比如

(1)如给定一个无序数组,查找最小/大的k个数,或者叫前k小/大的所有数。

剖析:思路是一样,只不过在最后返回的时候,要把k左边的所有的数返回即可。

(2)给定一个大小为n数组,如果已知这个数组中,有一个数字的数量超过了一半,如何才能快速找到该数字?

剖析:有一个数字的数量超过了一半,隐含的条件是在数组排过序后,中位数字就是n/2的下标,这个index的值必定是该数,所以就变成了查找数组第n/2的index的值,就可以利用快排分区找基准的思想,来快速求出。当然这只是解法的一种。

下面我们看下,从无序数组,如何查找第K小的值,也就是按照上面第四种思路,实现的代码如下:

package cn.mothin.util.encrypt;

/**
 * @Auther: Administrator
 * @Date: 2019/7/4 10:40
 * @Description:
 */
public class test {

    public static int quickSortFindRaidx
            (int a[], int left, int right
            ) {

        int pivot = a[left];

        int i = left;

        int j = right;
        while (i != j) {
//找右边第一个小于基准点的数字
            while (a[j] >= pivot && i < j) j--;
//做右边第一个大于基准点的数字
            while (a[i] <= pivot && i < j) i++;
            if (i < j) {
//进行交换int temp = a[i];a[i] = a[j];a[j] = temp;
            }
        }
//基准归位
        a[left] = a[i];a[i] = pivot;
        return i;

    }

    public static int findKthSmall
            (int a[],

             int left, int right, int k
            ) {
        if (k <= 0 || k > a.length) {
            return -1;
//超出查询范围,直接返回-1
        }
//返回基准点的下标,从0开始
        int pivotIndex = quickSortFindRaidx(a, left, right);

//包含基准点在内的左边的数字个数
        int leftNumCount = pivotIndex + 1;
//说明当前基准下标的值就是我们要找的
        if (leftNumCount == k)

        {
            return a[pivotIndex];

        }//说明要找的数,在基准点的左边,继续在左边部分递归查找
        if (leftNumCount > k) {
            return findKthSmall(a, left, pivotIndex, k);
        } else {//说明要找的数,在基准点的右边,继续在右边部分递归查找
            return findKthSmall
                    (a, pivotIndex + 1, right, k);

        }
    }


    public static void main(String[] args) {
        int[] arr = {12, 3, 5, 7, 4, 19, 26};
        int kthMin = findKthSmall(arr, 0, arr.length - 1, 1);
        System.out.println(kthMin);


    }

}

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 题目要求在一个无序整型数组中找出第k小的元素,即按从小到大排序后的第k个位置上的元素。 解决这个问题可以使用快速选择算法,它是快速排序算法的变种。快速选择算法的基本思想是:选择一个枢纽元素,将数组分为两个部分,一部分小于枢纽元素,一部分大于枢纽元素,然后根据枢纽元素所在的位置与k的大小关系,递归地在其中一个部分中查找第k小的元素。 具体实现可以参考以下代码: ```python def quick_select(arr, k): if len(arr) == 1: return arr[0] pivot = arr[0] left = [x for x in arr if x < pivot] right = [x for x in arr if x > pivot] equal = [x for x in arr if x == pivot] if k <= len(left): return quick_select(left, k) elif k > len(left) + len(equal): return quick_select(right, k - len(left) - len(equal)) else: return pivot ``` 其中,arr为待查找数组,k为要查找的第k小元素的位置。首先选择数组的第一个元素作为枢纽元素,然后将数组分为三个部分:小于枢纽元素的部分、大于枢纽元素的部分和等于枢纽元素的部分。根据k与左部分和等于部分的长度的大小关系,递归地在左部分或右部分中查找第k小的元素,或者直接返回枢纽元素。 时间复杂度为O(n),空间复杂度为O(n)。 ### 回答2: 题目要求我们从一个无序数组中寻找第k小的元素,可以使用快速选择算法来解决。快速选择算法和快速排序算法类似,都是基于分治法的思想,但是它只对需要查找的那一部分排序,而不是对整个数组进行排序。 我们可以选择一个元素作为主元,将数组中小于主元的元素放在左边,大于主元的元素放在右边,并记录主元的位置。如果主元的位置恰好是第k-1个位置,就找到了第k小的元素;如果主元的位置大于k-1,说明要查找元素在左边,再递归左半部分;如果主元的位置小于k-1,说明要查找元素在右边,再递归右半部分。 下面是快速选择算法的详细步骤: 1. 随机选择一个元素作为主元 2. 将小于主元的元素放在左边,大于主元的元素放在右边,并记录主元的位置 3. 如果主元的位置恰好是第k-1个位置,返回主元;如果主元的位置大于k-1,递归左半部分;如果主元的位置小于k-1,递归右半部分 4. 重复步骤1~3,直到找到第k小的元素 时间复杂度为O(n),因为每次只需要递归其中一半的元素。快速选择算法比直接对整个数组进行排序要快得多。 下面是快排算法的Python代码实现: ``` import random def partition(nums, left, right): pivot_index = random.randint(left, right) pivot = nums[pivot_index] nums[pivot_index], nums[right] = nums[right], nums[pivot_index] store_index = left for i in range(left, right): if nums[i] < pivot: nums[i], nums[store_index] = nums[store_index], nums[i] store_index += 1 nums[right], nums[store_index] = nums[store_index], nums[right] return store_index def quick_select(nums, left, right, k): if left == right: return nums[left] pivot_index = partition(nums, left, right) if pivot_index == k - 1: return nums[pivot_index] elif pivot_index > k - 1: return quick_select(nums, left, pivot_index - 1, k) else: return quick_select(nums, pivot_index + 1, right, k) def find_kth_smallest(nums, k): return quick_select(nums, 0, len(nums) - 1, k) ``` 其中partition函数是快排算法的分区函数,将小于主元的元素放在左边,大于主元的元素放在右边,并返回主元的位置。quick_select函数是快速选择算法的实现,不断递归左半部分或右半部分,直到找到第k小的元素为止。find_kth_smallest函数是快速选择算法的接口函数,这里提供的是以0为起始下标的数组。 ### 回答3: 一、暴力解法 我们可以对该无序数组进行排序,然后直接返回第k个元素即可。但是该算法时间复杂度为O(nlogn),显然当n非常大时,其效率会非常低。因此,我们需要更高效的算法。 二、快速选择算法 快速选择算法(Quick Select)是快速排序算法的一个变种,它可以在平均情况下以O(n)的时间复杂度寻找一个无序数组中第k小(大)的元素,这个算法的最坏情况下时间复杂度为O(n^2)。 三、算法步骤 1.选择数组中任意一个元素,称之为枢轴元素(pivot); 2.将数组中所有小于等于枢轴元素的数移到它的左边,所有大于等于枢轴元素的数移到它的右边; 3.如果枢轴元素在第k个位置上,那么直接返回; 4.如果枢轴元素在第k个位置上的左边,那么递归地在左边继续查找第k小的数; 5.如果枢轴元素在第k个位置上的右边,那么递归地在右边继续查找第k小的数。 四、代码实现 ``` int findKthSmallest(int[] nums, int k) { return quickSelect(nums, 0, nums.length - 1, k); } int quickSelect(int[] nums, int left, int right, int k) { int pivot = nums[left]; int i = left, j = right; while (i < j) { while (i < j && nums[j] >= pivot) j--; nums[i] = nums[j]; while (i < j && nums[i] <= pivot) i++; nums[j] = nums[i]; } nums[i] = pivot; // 将枢轴元素放到正确的位置上,此时i等于枢轴元素的位置 if (i == k - 1) { return nums[i]; } else if (i > k - 1) { return quickSelect(nums, left, i - 1, k); } else { return quickSelect(nums, i + 1, right, k); } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值