215. 数组中的第K个最大元素

166 阅读2分钟

题目

力扣(leetcode)

给定整数数组 nums 和整数 k,请返回数组中第 **k** 个最大的元素。 请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。 你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

代码

package main

import (
   "fmt"
   "math/rand"
)

func partition(nums []int, left int, right int) int {
   pivot := left + rand.Intn(right-left+1)
   nums[pivot], nums[right] = nums[right], nums[pivot]
   store := left
   for i := left; i < right; i++ {
      if nums[i] > nums[right] {
         nums[i], nums[store] = nums[store], nums[i]
         store++
      }
   }
   nums[store], nums[right] = nums[right], nums[store]
   return store
}

func findSelect(nums []int, left int, right int, k int) int {
   if left == right {
      return nums[left]
   }
   store := partition(nums, left, right)
   if store == k {
      return nums[store]
   } else if store > k {
      return findSelect(nums, left, store-1, k)
   } else {
      return findSelect(nums, store+1, right, k)
   }
}

func findKthLargest(nums []int, k int) int {
   return findSelect(nums, 0, len(nums)-1, k-1)
}

func main() {
   nums := []int{2, 3, 3}
   k := 3
   fmt.Printf("第 %d 大的元素是 %d\n", k, findKthLargest(nums, k))
}

解题思路

  1. 首先这个题要求是O(n)的时间复杂度,那么肯定不是先排序数组,再去查找
  2. 现在就想到分治,因为分治的时间复杂度是O(n), 因为分治的每一层都将规模缩小一半,O(n) (1 + 1/2 + 1/4 + ...),这是平均情况下, 这样下来就是O(n) * 2, 也就是O(n)
  3. 但是呢,如果用分治排序好,再去查找那就是 O(n^2), 肯定不行,所以就想到在分治的过程中去判断,因为每一次分治都能确定一个值的位置,此时判断这个位置是不是我们想要的,就可以了
  4. 至于分治的算法,首先先确定一个基准点,但是这个基准点的位置要随机,因为比如选最右边,那么如果数组是排序好的,那么每次只会筛选掉一个值,此时时间复杂度就是O(n^2),所以要随机选择基准点,这样平均下来每次都能筛选一半,用 left + rand.intn(right-left+1), 这样能得到随机的基准点,然后把基准点放到最右边,这样只处理最右边前面的数据就行,操作简单
  5. 从left开始遍历,和基准点值pivot比较,大于基准点值的情况就挨个从left开始放,比如放了两个大于基准点值的值,那么第三个位置和最右边互换,此时基准点左边是大于pivot的值,右边是小于pivot的值,此时pivot的位置就是确定的,判断pivot的位置是不是想要的,是就返回,不然就接着找pivot左边的区域和右边的区域,如果找到left和right值相同,可以理解为别的位置都找遍了,只剩下你了,那么你就是要找的地方