快速选择
利用了快速排序的思想,求解第 K 个元素的问题。利用的是快速排序的划分思想 partition() 进行实现的。需要先打乱数组,不然最坏的情况复杂度为 O(N^2)。
快速排序的每次 partition() 过程中都排好一个位置。如果排好的是正数第K个位置,那么它就是第K小的元素。
堆
预备知识:
- 堆是一个完全二叉树
- 完全二叉树:除开最后一层,其他层的节点数都达到最大,最后一层的所有节点都集中在左边。
- 大根堆:根节点为最大值,每个结点的值大于等于孩子结点的值。
- 小根堆:根节点的值为最小值,每个根节点的值都小于等于孩子结点的值。
- 堆的存储:用数组来存储。
- 对于节点i,子节点为2i+1 与 2i+2
- 堆排序算法(以大根堆(heapify)为例):
- 对每一个非叶子节点,从下至上,从右至左,做 shiftDown 下沉操作。操作后根节点为最大值,将其与最后一个结点交换。
- 除开最后一个节点,其余节点继续转化成大根堆,此时根节点为次最大,将其与最后一个结点交换。
- 重复2,直到堆中元素个数为1,排序完成。
const swap = (arr, i, j) => {
[arr[i], arr[j]] = [arr[j], arr[i]]
}
const shiftDowm = (arr, i, length) => {
for (let j = 2 * i + 1; j < length; j = j * 2 + 1) {
let temp = arr[i]
if (j + 1 < length && arr[j + 1] > arr[j]) {
j++
}
if (arr[j] > temp) {
swap(arr, i, j)
i = j
} else {
break;
}
}
}
const heapSort = (arr) => {
for (let i = Math.floor(arr.length / 2 - 1); i >= 0; i--) {
shiftDowm(arr, i, arr.length)
}
for (let i = arr.length - 1; i >= 0; i--) {
swap(arr, 0, i)
shiftDowm(arr, 0, i)
}
}
用于求解第 K 个最小元素问题。可以维护一个大小为K的大根堆。大根堆的堆顶元素即为该堆的最大值。每次遍历新元素时,如果比堆顶元素小,则把堆顶元素去除,将新元素加到堆中,调整堆。遍历完之后,堆里面的K个元素是从小到大排列的,堆顶即为第K个最小元素。
也可以用于求解第K个最大元素的问题。维护一个K大小的小根堆。
JS 实现堆排序
Kth Element
LeetCode 215
Input: [3,2,1,5,6,4] and k = 2 Output: 5
题目描述:找出倒数第K大的元素
排序
时间复杂度 O(NlogN),空间复杂度 O(1)
var findKthLargest = function(nums, k) {
nums.sort();
return nums[nums.length-k]
};
先用自带的排序方法进行排序,然后再取倒数第k个元素
堆
时间复杂度 O(NlogK),空间复杂度 O(K)。
快速排序
时间复杂度 O(N),空间复杂度 O(1)
var findKthLargest = function (nums, k) {
k = nums.length - k
let l = 0, r = nums.length - 1
while (l < r) {
let res = partition(nums, l, r)
if (res === k) break
else if (res > k) r = res - 1
else l = res + 1
}
return nums[k]
};
const partition = (nums, i, j) => {
let l = i, r = j + 1
while (true) {
while (l < j && nums[++l] < nums[i]);
while (r > i && nums[--r] > nums[i]);
if (l >= r) break;
[nums[l], nums[r]] = [nums[r], nums[l]]
}
[nums[i], nums[r]] = [nums[r], nums[i]]
return r
}
通过每次划分,找出倒数第k个元素就行。
出现频率最多的 k 个元素
LeetCode 347
Given [1,1,1,2,2,3] and k = 2, return [1,2].
设置若干个桶,桶的下标表示出现的频率,即第 i 个桶里存储的数出现的频率为 i。都存到桶里之后,根据桶的频率进行排序,从后往前遍历,最先得到的k个元素即为出现频率最多的k个元素。
var topKFrequent = function (nums, k) {
let map = new Map()
for (i of nums) {
map.set(i, map.get(i) + 1 || 1)
}
let sortedArray = [...map.entries()].sort((a, b) => b[1] - a[1])
let res = []
for (let i = 0; i < k; i++) {
res.push(sortedArray[i][0])
}
return res
};
按照字符串出现次数对字符串进行排序
LeetCode 451
Input: "tree" Output: "eert"
同上题,先用桶存所有字符,按照字符的频率进行排序,从最多频率往最少频率进行打印即可。
var frequencySort = function (s) {
let map = new Map()
for (let i of s) {
map.set(i, map.get(i) + 1 || 1)
}
let res = [];
[...map.entries()].sort((a, b) => b[1] - a[1]).forEach(cur => {
for (let i = 0; i < cur[1]; i++) {
res.push(cur[0])
}
})
return res.join('')
};
荷兰国旗问题
LeetCode 75
Input: [2,0,2,1,1,0] Output: [0,0,1,1,2,2]
题目描述:按照 0 1 2 顺序进行排序,不能使用原始的排序
运用划分的思想,等于零的放左边,等于二的划分到右边,最后中间就是等于1的了。
var sortColors = function (nums) {
let zero = -1, one = 0, two = nums.length
while (one < two) {
if (nums[one] === 0) {
zero++
[nums[zero], nums[one]] = [nums[one], nums[zero]]
one++
} else if (nums[one] === 2) {
two--
[nums[two], nums[one]] = [nums[one], nums[two]]
} else {
one++
}
}
return nums
};