题目
LeetCode.703.数据流中的第 K 大元素(简单)
设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。
请实现 KthLargest 类:
KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。
int add(int val) 将 val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。
题解
解法1:堆
第k大的数就是数组按照从大到小的顺序下的第k位。
利用小顶堆的特性,堆只保留k个元素,第1大到第k大的数都在堆里,第k大的数正好是堆顶。
如果要插入的数比堆顶元素大,堆顶元素就变为了第k+1大的数,应该从堆中舍弃,于是就将堆顶元素替换为新元素,再调整堆使其满足小顶堆的特性,此时堆顶依然是第k大的数。
如果要插入的数比堆顶元素小,则忽略,因为第k+1大、k+2大等等的数字我们并不关心。
建立堆的时间是O(n),一次向下调整堆的时间复杂度是O(log k)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
| class KthLargest( private val k: Int, nums: IntArray ) {
private val heap: IntArray
private var length = nums.size
init { heap = if (k == length + 1) { IntArray(length + 1).apply { nums.copyInto(this) } } else nums
buildMinHeap(heap, length)
keepHeapSize(heap, length, k) }
fun add(`val`: Int): Int { if (length < heap.size) { heap[length] = heap[0] length++ } else { if (`val` <= heap[0]) { return heap[0] } }
heap[0] = `val` sink(heap, 1, k) return heap[0] }
private fun keepHeapSize(nums: IntArray, originalSize: Int, size: Int) { if (originalSize <= size) { return } for (length in originalSize downTo size + 1) { nums.swap(0, length - 1) sink(nums, 1, length - 1) } }
private fun buildMinHeap(nums: IntArray, length: Int) { for (parent in length / 2 downTo 1) { sink(nums, parent, length) } }
private fun sink(nums: IntArray, k: Int, length: Int) { var parent = k while (parent <= length / 2) { var child = parent * 2 if (child < length && nums[child - 1] > nums[child]) child++ if (nums[parent - 1] <= nums[child - 1]) break nums.swap(parent - 1, child - 1) parent = child } }
private fun IntArray.swap(i: Int, j: Int) { val tmp = this[i] this[i] = this[j] this[j] = tmp } }
|
解法2:二叉搜索树
二叉搜索树法原理参见二叉搜索树探索卡片
https://leetcode-cn.com/explore/learn/card/introduction-to-data-structure-binary-search-tree/66/conclusion/182/
鉴于我们同时需要插入和搜索操作,为什么不考虑使用一个二叉搜索树结构存储数据呢? 我们知道,对于二叉搜索树的每个节点来说,它的左子树上所有结点的值均小于它的根结点的值,右子树上所有结点的值均大于它的根结点的值。 换言之,对于二叉搜索树的每个节点来说,若其左子树共有m个节点,那么该节点是组成二叉搜索树的有序数组中第m + 1个值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| class KthLargest(k: Int, nums: IntArray) { private var bst: TreeNode? = null private val kth = k
init { nums.forEach { bst = BST.insert(bst, it) } }
fun add(`val`: Int): Int { bst = BST.insert(bst, `val`) val node = BST.search(bst, kth) return node!!.`val` }
object BST { fun insert(root: TreeNode?, value: Int): TreeNode? { if (root == null) { return TreeNode(value, 1) } if (value < root.`val`) { root.left = insert(root.left, value) } else if (value > root.`val`) { root.right = insert(root.right, value) } root.count++ return root }
fun search(root: TreeNode?, k: Int): TreeNode? { if (root == null) { return root } val leftNodeCount = root.left?.count ?: 0 val rightNodeCount = root.right?.count ?: 0 val currentNodeCount = root.count - leftNodeCount - rightNodeCount return when { k <= rightNodeCount -> search(root.right, k) k > rightNodeCount + currentNodeCount -> search(root.left, k - rightNodeCount - currentNodeCount) else -> root } } }
class TreeNode(var `val`: Int, var count: Int) { var left: TreeNode? = null var right: TreeNode? = null } }
|