本文仅供自己复习
原本按常理来说这个应该收纳到算法总结里,结果一看那个算法总结太长了哈哈哈,找时间重新理一下那个文档
TopK类问题一般就是指找第K个最大或者最小
a、排序
V8在7.0前的排序,如果数只有10个以下就是用的插入排序O(n2),如果是以上就用快速排序O(nlogn),但是快速排序是不稳定的
不稳定的意思是如果在一个数组中【1,0,1】排序后需要让相等的值他们的先后位置跟排序前一样,如第一个1必须在第二个1后面,但是快排以及选择排序是非稳定的,只有冒泡、插入、归并是稳定排序
所以在7.0之后,首先对数据少的先使用插入排序,然后再使用归并排序(这样就相当于使用了混合排序)
/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/
var findKthLargest = function(nums, k) {
nums = nums.sort((a, b) => b - a)
return nums[k - 1]
};
时间复杂度也是O(nlogn)
b、快排
快排在这里的精髓就是,随机找一个基点,毕竟如果用第一个或者最后一个极有可能时间复杂度为O(n2)
/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/
var findKthLargest = function(nums, k) {
let swap = function(nums, n, m) {
let temp = nums[n]
nums[n] = nums[m]
nums[m] = temp
}
let getPiviot = function(nums, left, right) {
let pivot = left
// 这里从left开始而不是从pivot+1开始,是因为担心万一只有两个数的情况(这样可以少写一个判断)
while(left < right) {
while(nums[right] <= nums[pivot] && left < right) {
right--
}
while(nums[left] >= nums[pivot] && left < right) {
left++
}
swap(nums, left, right)
}
swap(nums, left, pivot)
return left
}
let quickSort = function(nums, left, right) {
if(left >= right) return
// 求随机整数值Math.floor(Math.random() * (max - min)) + min 结果是[min, max)里的值(取不到max)
// 想取到max就让max + 1
let pivot = Math.floor(Math.random() * (right + 1 - left)) + left
// 让基准放第一个去
swap(nums, left, pivot)
let index = getPiviot(nums, left, right)
if(index < k - 1) {
quickSort(nums, index + 1, right)
}
if(index > k - 1) {
quickSort(nums, left, index - 1)
}
}
quickSort(nums, 0, nums.length - 1)
return nums[k - 1]
};
使用快排的时间复杂度是O(n)
c、大顶堆
关于堆排序可以看堆排序图解
首先要明确大顶堆就是父节点一定比孩子大,小顶堆就是父节点一定比孩子小
从图中我们可以看出,大顶堆是降序,小顶堆是升序
另外从图中也可以看出,当某节点的下标为 i 时,它的父节点下标是 Math.floor(i / 2),左节点的下标是 i * 2 + 1,右节点的下标是 i * 2 + 2
大致就这么个意思
所以我们要找topK,其实只要构建一个只有K个元素的大顶堆,堆顶就是要求的结果,在这里首先要说一下,使用堆
/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/
var findKthLargest = function(nums, k) {
let swap = function(n, m) {
let temp = nums[n]
nums[n] = nums[m]
nums[m] = temp
}
let buildHeap = function(index, max) {
let newIndex = index
while(true) {
let left = index * 2 + 1
let right = index * 2 + 2
if(left < max && nums[left] <= nums[index]) {
newIndex = left
}
// 这里要比较的是新的index!!这是重点
if(right < max && nums[right] <= nums[newIndex]) {
newIndex = right
}
if(index !== newIndex) {
swap(index, newIndex)
index = newIndex
} else {
break
}
}
}
// 维持一个k个元素的小顶堆
// 这样在堆里的就是最大的k个元素,而堆顶就是第k大
// 从最下面那个叶子节点的父节点开始
for(let i = Math.floor((k - 1) / 2); i >= 0; i--) {
buildHeap(i, k)
}
// 之后再把数据和堆顶一个个地比较
// 如果堆顶大一点,就不管了,如果堆顶小,就把这个数变为堆顶
for(let i = k; i < nums.length; i++) {
if(nums[0] < nums[i]) {
swap(0, i)
buildHeap(0, k)
}
}
return nums[0]
};
使用堆的时间复杂度是O(nlogn),虽然没快排快,但是碰到动态列表情况就很好
d、冒泡排序
/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/
var findKthLargest = function(nums, k) {
let swap = function(n, m) {
let temp = nums[n]
nums[n] = nums[m]
nums[m] = temp
}
for(let i = 0; i < k; i++) {
// 如果后面都没有进行交换,就说明已经排好序了
let flag = false
for(let j = 0; j < nums.length - i - 1; j++) {
if(nums[j] > nums[j + 1]) {
swap(j, j + 1)
flag = true
}
}
if(!flag) {
break
}
}
return nums[nums.length - k]
};
不多说,时间复杂度为O(n)