赞
踩
这题其实只是要先计数,剩下的只是排序,与 LeetCode215 数组中的第K个最大元素 类似。
// javascript var topKFrequent = function(nums, k) { let res = new Array(); const occurrences = new Map(); for (const n of nums) { occurrences.set(n, (occurrences.get(n) || 0) + 1); } // 桶排序 // 将频率作为数组下标,对于出现频率不同的数字集合,存入对应的数组下标 const list = new Array(nums.length + 1); for (const [num, count] of occurrences.entries()) { // 获取出现的次数作为下标 if (list[count] === undefined) list[count] = new Array(); list[count].push(num); } // 倒序遍历数组获取出现顺序从大到小的排列 for (let i = list.length - 1; i >= 0 && res.length < k; i--) { if (list[i] !== undefined) { res = res.concat(list[i]); // res 有被改动,不能声明为 const } } return res; };
// javascript var topKFrequent = function(nums, k) { const occurrences = new Map(); for (const n of nums) { occurrences.set(n, (occurrences.get(n) || 0) + 1); } const queue = new MinPriorityQueue(); for (let [currNum, currPriority] of occurrences.entries()) { if (queue.size() < k) { queue.enqueue(currNum, currPriority); } else { let frontEntry = queue.front(); if (frontEntry.priority < currPriority) { queue.dequeue(); queue.enqueue(currNum, currPriority); } } } return queue.toArray().map(el => el.element); };
调用了 JS 的小顶堆 API,详细用法见:priority-queue/README.md
手写小顶堆:
// javascript var topKFrequent = function(nums, k) { const occurrences = new Map(); for (const n of nums) { occurrences.set(n, (occurrences.get(n) || 0) + 1); } const heap = Array.from(occurrences); // 构建大小为k的小顶堆 buildMinHeap(heap, k); for (let i = k; i < heap.length; i++) { if (heap[i][1] > heap[0][1]) { swap(heap, i, 0); minHeapify(heap, 0, k); } } return heap.slice(0, k).map(el => el[0]); }; // 原地建堆,从后往前,自上而下式建小顶堆 const buildMinHeap = (heap, size) => { // 从最后一个非叶子节点开始,自下而上堆化 for(let i = (size >> 1) - 1; i >= 0; i--) { minHeapify(heap, i, size); } } // 堆化 const minHeapify = (heap, i, size) => { let minIndex = i; const leftIndex = (i << 1) + 1, rightIndex = (i << 1) + 2; if (leftIndex < size && heap[leftIndex][1] < heap[minIndex][1]) minIndex = leftIndex; if (rightIndex < size && heap[rightIndex][1] < heap[minIndex][1]) minIndex = rightIndex; if (minIndex !== i) { swap(heap, minIndex, i); minHeapify(heap, minIndex, size); } }; // 交换 const swap = (heap, i , j) => { [heap[i], heap[j]] = [heap[j], heap[i]]; }
或者封装成一个类:
// javascript class MinHeap { constructor() {this.heap = [];} swap(i, j) {[this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]];} // 交换节点 getParentIndex(i) {return ((i - 1) >> 1);} // 获取父节点, 2进制操作,取商: Math.floor((i - 1) / 2) getLeftIndex(i) {return (i << 1) + 1;} getRightIndex(i) {return (i << 1) + 2;} shiftUp(index) { // 上移 if (index == 0) return; const parentIndex = this.getParentIndex(index); // 父节点大于当前节点 if (this.heap[parentIndex] && this.heap[parentIndex].priority > this.heap[index].priority) { this.swap(parentIndex, index); this.shiftUp(parentIndex); } } shiftDown(index) { // 下移 let minIndex = index; const leftIndex = this.getLeftIndex(index); const rightIndex = this.getRightIndex(index); if (this.heap[leftIndex] && this.heap[leftIndex].priority < this.heap[minIndex].priority) { minIndex = leftIndex; } if (this.heap[rightIndex] && this.heap[rightIndex].priority < this.heap[minIndex].priority) { minIndex = rightIndex; } if (minIndex !== index) { this.swap(minIndex, index); this.shiftDown(minIndex); } } // 将值插入堆的底部,即数组的尾部。 // 然后_上移:将这个值和它的父节点进行交换,直到父节点小于等于这个插入的值 // 大小为k的堆中插入元素的时间复杂度为O(logK) enqueue(value) { this.heap.push(value); this.shiftUp(this.size() - 1); } // 删除堆顶 // 用数组尾部元素替换堆顶(直接删除堆顶会破坏堆结构)。 // 然后下移: 将新堆顶和它的子节点进行交换,直到子节点大于等于这个新堆顶。 // 大小为k的堆中删除堆顶的时间复杂度为O(logk)。 dequeue() { // 因为下面的操作会先 enqueue 再 dequeue 所以不会出现 heap 大小为 1 的情况 this.heap[0] = this.heap.pop(); this.shiftDown(0); } front() {return this.heap[0];} size() {return this.heap.length;} } var topKFrequent = function(nums, k) { const occurrences = new Map(); for (const n of nums) { occurrences.set(n, (occurrences.get(n) || 0) + 1); } const h = new MinHeap(); for (const [element, priority] of occurrences.entries()) { h.enqueue({priority, element}); if (h.size() > k) { h.dequeue(); } } return h.heap.map(el => el.element); };
// javascript var topKFrequent = function(nums, k) { const occurrences = new Map(); for (const n of nums) { occurrences.set(n, (occurrences.get(n) || 0) + 1); } const list = Array.from(occurrences); buildMaxHeap(list); for (let i = list.length - 1; i >= list.length - k; i--) { swap(list, 0, i); maxHeapify(list, 0, i); } return list.slice(list.length - k).map(element => element[0]); }; const buildMaxHeap = (list) => { for (let i = (list.length >> 1) - 1; i >= 0; i--) { maxHeapify(list, i, list.length); } }; const maxHeapify = (list, i, size) => { let maxIndex = i, l = (i << 1) + 1, r = (i << 1) + 2; if (l < size && list[l][1] > list[maxIndex][1]) maxIndex = l; if (r < size && list[r][1] > list[maxIndex][1]) maxIndex = r; if (maxIndex !== i) { swap(list, i, maxIndex); maxHeapify(list, maxIndex, size); } }; const swap = (list, i, j) => { [list[i], list[j]] = [list[j], list[i]]; };
时间复杂度:
O
(
k
l
o
g
n
)
O(klogn)
O(klogn),n 是数组中不同数字的数量,堆中有 n 个元素,移除堆顶的时间是 logn,重复操作了 k 次。
空间复杂度:
O
(
n
)
O(n)
O(n),n 是数组中不同数字的数量,哈希表和堆的空间。
这题求前 K 个高频元素,排序顺序是从大到小进行排列。
// javascript var topKFrequent = function (nums, k) { // 统计出现次数 const occurrences = new Map(); for (const n of nums) { occurrences.set(n, (occurrences.get(n) || 0) + 1); } const list = Array.from(occurrences); quickSelect(list, 0, list.length - 1, k); return list.slice(0, k).map(element => element[0]); }; var partition = function(list, left, right) { // Math.random() * (right - left + 1) => [0, right - left + 1) // Math.floor([0, right - left + 1)) => 0, 1, 2, ..., right - left // left + Math.floor([0, right - left + 1)) => left, left + 1, ..., right const pivot = list[left]; let i = left, j = right; while (i < j) { while (i < j && list[j][1] <= pivot[1]) j--; // 从后往前,找到比pivot更大的数 list[i] = list[j]; // 将更大的数放入左边 while (i < j && list[i][1] >= pivot[1]) i++; // 从前往后,找到比pivot更小的数 list[j] = list[i]; // 将更小的数放入右边 } list[i] = pivot; // 将pivot放入最终位置 return i; }; var quickSelect = function(list, left, right, k) { if (left >= right) return; let index = partition(list, left, right); // index - left = k - 1 时,因为题目说可以按任意顺序返回答案,所以不需要再排序 // index - left > k - 1 时,前 k 个高频元素在左边 if (index - left > k - 1) quickSelect(list, left, index - 1, k); // index - left < k - 1 时,前 index - left + 1 个高频元素在左边 // 要去右边寻找剩下的前 k - (index - left + 1) 个高频元素 if (index - left < k - 1) quickSelect(list, index + 1, right, k - (index - left + 1)); };
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。