2020-03-02:在无序数组中,如何求第K小的数?

2020-03-02:在无序数组中,如何求第K小的数?

福哥答案2021-03-02:

1.堆排序。时间复杂度:O(N*lgK)。有代码。
2.单边快排。时间复杂度:O(N)。有代码。
3.bfprt算法。时间复杂度:O(N)。有代码。

代码用golang编写,代码如下:

package main

import (
    "container/heap"
    "fmt"
    "math/rand"
    "sort"
)

func main() {

    //1 2 3 4 5 6 7
    arr := []int{1, 2, 3, 4, 5, 10, 9, 8, 7, 6}
    ret := minKth1(arr, 7)
    fmt.Println("1.堆排序:", ret)

    ret = minKth2(arr, 7)
    fmt.Println("2.单边快排:", ret)

    ret = minKth3(arr, 7)
    fmt.Println("3.bfprt算法:", ret)

}

// 利用大根堆,时间复杂度O(N*logK)
func minKth1(arr []int, k int) int {
    maxHeap := &IntHeap{}
    heap.Init(maxHeap)
    for i := 0; i < k; i++ {
        heap.Push(maxHeap, arr[i])
    }
    for i := k; i < len(arr); i++ {
        heap.Push(maxHeap, arr[i])
        heap.Pop(maxHeap)
        //heap.Push(maxHeap, arr[i])
    }
    return heap.Pop(maxHeap).(int)
}

type IntHeap sort.IntSlice

func (h IntHeap) Len() int           { return len(h) }
func (h IntHeap) Less(i, j int) bool { return !(h[i] < h[j]) }
func (h IntHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }

//func (h IntHeap) Len() int           { return sort.IntSlice(h).Len() }
//func (h IntHeap) Less(i, j int) bool { return !sort.IntSlice(h).Less(i, j) }
//func (h IntHeap) Swap(i, j int)      { sort.IntSlice(h).Swap(i, j) }

func (h *IntHeap) Push(x interface{}) {
    //fmt.Println("push----")
    // Push and Pop use pointer receivers because they modify the slice's length,
    // not just its contents.
    *h = append(*h, x.(int))
}

func (h *IntHeap) Pop() interface{} {

    old := *h
    n := len(old)
    x := old[n-1]
    *h = old[0 : n-1]
    return x
}

// 改写快排,时间复杂度O(N)
// k >= 1
func minKth2(arr []int, k int) int {
    arrc := make([]int, len(arr))
    copy(arrc, arr)
    return process2(arrc, 0, len(arr)-1, k-1)
}

// arr 第k小的数
// process2(arr, 0, N-1, k-1)
// arr[L..R]  范围上,如果排序的话(不是真的去排序),找位于index的数
// index [L..R]
func process2(arr []int, L int, R int, index int) int {
    if L == R { // L = =R ==INDEX
        return arr[L]
    }
    // 不止一个数  L +  [0, R -L]
    pivot := arr[L+rand.Intn(R-L)]
    rang := partition(arr, L, R, pivot)
    if index >= rang[0] && index <= rang[1] {
        return arr[index]
    } else if index < rang[0] {
        return process2(arr, L, rang[0]-1, index)
    } else {
        return process2(arr, rang[1]+1, R, index)
    }
}

func partition(arr []int, L int, R int, pivot int) []int {
    less := L - 1
    more := R + 1
    cur := L
    for cur < more {
        if arr[cur] < pivot {
            less++
            arr[less], arr[cur] = arr[cur], arr[less]
            cur++
        } else if arr[cur] > pivot {
            more--
            arr[cur], arr[more] = arr[more], arr[cur]
        } else {
            cur++
        }
    }
    return []int{less + 1, more - 1}
}

// 利用bfprt算法,时间复杂度O(N)
func minKth3(arr []int, k int) int {
    arrc := make([]int, len(arr))
    copy(arrc, arr)
    return bfprt(arrc, 0, len(arr)-1, k-1)
}

// arr[L..R]  如果排序的话,位于index位置的数,是什么,返回
func bfprt(arr []int, L int, R int, index int) int {
    if L == R {
        return arr[L]
    }
    // L...R  每五个数一组
    // 每一个小组内部排好序
    // 小组的中位数组成新数组
    // 这个新数组的中位数返回
    pivot := medianOfMedians(arr, L, R)
    rang := partition(arr, L, R, pivot)
    if index >= rang[0] && index <= rang[1] {
        return arr[index]
    } else if index < rang[0] {
        return bfprt(arr, L, rang[0]-1, index)
    } else {
        return bfprt(arr, rang[1]+1, R, index)
    }
}

// arr[L...R]  五个数一组
// 每个小组内部排序
// 每个小组中位数领出来,组成marr
// marr中的中位数,返回
func medianOfMedians(arr []int, L int, R int) int {
    size := R - L + 1
    offset := 0
    if size%5 != 0 {
        offset = 1
    }
    mArr := make([]int, size/5+offset)
    for team := 0; team < len(mArr); team++ {
        teamFirst := L + team*5
        // L ... L + 4
        // L +5 ... L +9
        // L +10....L+14
        mArr[team] = getMedian(arr, teamFirst, getMin(R, teamFirst+4))
    }
    // marr中,找到中位数
    // marr(0, marr.len - 1,  mArr.length / 2 )
    return bfprt(mArr, 0, len(mArr)-1, len(mArr)/2)
}

func getMedian(arr []int, L int, R int) int {
    insertionSort(arr, L, R)
    return arr[(L+R)/2]
}

func insertionSort(arr []int, L int, R int) {
    for i := L + 1; i <= R; i++ {
        for j := i - 1; j >= L && arr[j] > arr[j+1]; j-- {
            arr[j], arr[j+1] = arr[j+1], arr[j]
        }
    }
}
func getMin(a int, b int) int {
    if a < b {
        return a
    } else {
        return b
    }
}

执行结果如下:
在这里插入图片描述


左神java代码
评论

可以使用快速选择算法来解决这个问题,其时间复杂度为O(N)。 快速选择算法基于快速排序,但不需要完全排序整个组。它通过每次选择一个基准元素并将组分为两个部分来工作,然后根据基准元素的位置来确定我们需要搜索的那个部分。 具体地,我们可以首先随机选择一个元素作为基准元素,然后将数组中比该元素小的元素放在它的左边,比该元素大的元素放在它的右边。假设基准元素的位置为p,则左边的子数组中有p个元素小于它,右边的子数组中有n-p-1个元素大于它。如果k=p,则基准元素即为第k小的元素;如果k<p,则我们需要在左边的子数组中继续搜索第k小的元素;如果k>p,则我们需要在右边的子数组中继续搜索第k-p-1小的元素。 下面是使用快速选择算法解第k小的的C代码: ``` #include <stdio.h> #include <stdlib.h> int partition(int* nums, int left, int right) { int pivot = nums[left]; int i = left + 1, j = right; while (i <= j) { while (i <= j && nums[i] < pivot) { i++; } while (i <= j && nums[j] >= pivot) { j--; } if (i < j) { int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } } int temp = nums[left]; nums[left] = nums[j]; nums[j] = temp; return j; } int quickSelect(int* nums, int left, int right, int k) { if (left == right) { return nums[left]; } int pivotIndex = partition(nums, left, right); if (k == pivotIndex) { return nums[k]; } else if (k < pivotIndex) { return quickSelect(nums, left, pivotIndex - 1, k); } else { return quickSelect(nums, pivotIndex + 1, right, k); } } int findKthSmallest(int* nums, int size, int k) { return quickSelect(nums, 0, size - 1, k - 1); } int main() { int nums[] = {3, 2, 1, 5, 6, 4}; int size = sizeof(nums) / sizeof(int); int k = 2; int kthSmallest = findKthSmallest(nums, size, k); printf("The %dth smallest element is %d\n", k, kthSmallest); return 0; } ``` 在这个例子中,我们使用组{3, 2, 1, 5, 6, 4}来演示如何找到第2小的元素。我们首先调用findKthSmallest函,并将nums组的大小和k值作为参传递。在findKthSmallest函中,我们调用quickSelect函来实际查找第k小的元素。在quickSelect函中,我们首先将left和right参传递给partition函,以便将组分为两个部分。然后,我们根据pivotIndex的值决定搜索哪个子组。如果k等于pivotIndex,则我们找到了第k小的元素;否则,我们递归调用quickSelect函来搜索左边或右边的子组。最后,我们在main函中打印出找到的第k小的元素
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

福大大架构师每日一题

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值