编码母题 / P1 / 经典算法 / 09

前K个高频元素

重要程度高价值难度中等预计完成35–45 分钟

题目是什么

输入整数数组 nums 和整数 k,返回出现频率最高的 k 个不同元素;结果按频率降序,频率相同时按数值升序。k 范围为 0 到不同元素个数,k=0 返回 []。

输入输出示例

输入 nums=[1,1,1,2,2,3]、k=2,返回 [1,2];[4,4,3,3]、k=2 返回 [3,4]。

练习起手式与验证

/*
 * 题目:前K个高频元素
 * 要求:输入整数数组 nums 和整数 k,返回出现频率最高的 k 个不同元素;结果按频率降序,频率相同时按数值升序。k 范围为 0 到不同元素个数,k=0 返回 []。
 * 示例:输入 nums=[1,1,1,2,2,3]、k=2,返回 [1,2];[4,4,3,3]、k=2 返回 [3,4]。
 */
function topKFrequent(nums, k) {}
console.log(topKFrequent([1, 1, 1, 2, 2, 3], 2)); // 预期:[1, 2]
console.log(topKFrequent([4, 4, 3, 3, 2], 2)); // 预期:[3, 4]
console.log(topKFrequent([], 0)); // 预期:[]
console.log(topKFrequent([5, 5, 6], 0)); // 预期:[]

解题思路与实现

展开解题思路

先计数,再用大小为 k 的最小堆保存当前最优候选,堆顶始终是最差的一项。

function topKFrequent(nums, k) {
  const counts = new Map();
  for (const value of nums) counts.set(value, (counts.get(value) || 0) + 1);
  if (!Number.isInteger(k) || k < 0 || k > counts.size)
    throw new RangeError("invalid k");
  const heap = [];
  const worse = (a, b) =>
    a.count !== b.count ? a.count < b.count : a.value > b.value;
  const swap = (a, b) => {
    [heap[a], heap[b]] = [heap[b], heap[a]];
  };
  function up(i) {
    while (i > 0) {
      const p = Math.floor((i - 1) / 2);
      if (!worse(heap[i], heap[p])) break;
      swap(i, p);
      i = p;
    }
  }
  function down(i) {
    while (true) {
      let best = i;
      const l = i * 2 + 1;
      const r = l + 1;
      if (l < heap.length && worse(heap[l], heap[best])) best = l;
      if (r < heap.length && worse(heap[r], heap[best])) best = r;
      if (best === i) return;
      swap(i, best);
      i = best;
    }
  }
  for (const [value, count] of counts) {
    heap.push({ value, count });
    up(heap.length - 1);
    if (heap.length > k) {
      swap(0, heap.length - 1);
      heap.pop();
      down(0);
    }
  }
  return heap
    .sort((a, b) => b.count - a.count || a.value - b.value)
    .map((item) => item.value);
}

// 三组以上可直接运行的验证。
console.log(topKFrequent([1, 1, 1, 2, 2, 3], 2)); // 预期:[1, 2]
console.log(topKFrequent([4, 4, 3, 3, 2], 2)); // 预期:[3, 4]
console.log(topKFrequent([], 0)); // 预期:[]
console.log(topKFrequent([5, 5, 6], 0)); // 预期:[]

时间复杂度:O(n + m log(k+1))。n 个输入先计数,m 个不同值各做一次至多 k 项的堆调整。

空间复杂度:O(m+k)。频次 Map 保存 m 项,小根堆最多保留 k 项。

下载原始 JS 文件