偶尔间看到一个问题:从海量数字中找到第k大的数
脑子里蹦出来的第一个念头是堆,复杂度O(nlogk)。后来想想好像在某些情况下可以用快速选择的思想,能达到O(n)。
快速选择的思想很简单,在快速排序的基础上实现,比如选择数字p为比较值,将一组数分为两半,我们将数字p的索引i与k比较就能知道第k个数字是在索引i的左边还是右边,还是恰好是i直接返回,接着只对包含k的那一边继续做相同过程就可以了。下面是go代码实现。
Tab: 其中加入了输出过程,来观察每一步的结果。
package main import ( "fmt" "math/rand" ) func main() { org := rand.Perm(10) value := 7 fmt.Println("原始切片: ", org) fmt.Printf(" 第%d个值: %d\n", value, findKthLargest(org, 0, len(org)-1, value)) fmt.Println("最终切片: ", org) } func findKthLargest(s []int, start, end, k int) int { p := s[end] l := start for i := start; i < end; i++ { if s[i] <= p { fmt.Println(" 交换: i-", i, " l-", l, " p-", p) s[l], s[i] = s[i], s[l] l++ } fmt.Println(" 过程中: ", s) } s[l], s[end] = s[end], s[l] fmt.Println("一轮结束: ", s) fmt.Println("-------") if l == k { return s[l] } else if l < k { return findKthLargest(s, l+1, end, k) } else { return findKthLargest(s, start, l-1, k) } }
但是用快速选择有限制,数字中不能包含相同的数字,否则会陷入循环。当然有重复数字的情况也可以用别的方式解决。
记录每天解决的一点小问题,积累起来就能解决大问题。