【路飞】算法与数据结构-前 K 个高频元素

98 阅读1分钟

不管全世界所有人怎么说,我都认为自己的感受才是正确的。无论别人怎么看,我绝不打乱自己的节奏。喜欢的事自然可以坚持,不喜欢的怎么也长久不了。

LeetCode:原题地址

题目要求

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

示例 1:

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

示例 2:

输入: nums = [1], k = 1
输出: [1]

提示:

  • 1 <= nums.length <= 105
  • k 的取值范围是 [1, 数组中不相同的元素的个数]
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

思路

实现一个最小堆,维护一个heap数组,长度为 k,存储数字本身,对应的频次是从低到高,当heap数组长度不够k时,就从后push推入数字,然后执行“上浮”,即元素在最小堆中,交换到它合适的位置。当heap数组长度够k时,如果新的数字的频次比堆顶的数字的频次高,我们将它去替换堆顶的数字,然后执行“下沉”,交换到最小堆中合适的位置。

最后,heap数组存的是频次从低到高的 k 个数字,然后我们逆序一下,输出即可。

const topKFrequent = (nums, k) => {
  const freq = {};       // 存储数字出现的频次
  const uniqueNums = []; // 不重复的数字
  for (const num of nums) {
    if (freq[num]) {     // 出现过,频次+1
      freq[num]++;
    } else {             // 没出现过,频次为1
      freq[num] = 1;
      uniqueNums.push(num);
    }
  }

  const heap = [];       // 代表heap的数组

  const swap = (i, j) => { // 交换heap数组的元素
    const t = heap[i];
    heap[i] = heap[j];
    heap[j] = t;
  };

  const bubbleUp = (index) => {
    while (index > 0) {
      const parent = (index - 1) >>> 1;  // 找到父节点在heap数组中的位置
      if (freq[heap[parent]] > freq[heap[index]]) { // 如果父节点数字对应的频率要高于插入的数字的频次
        swap(parent, index); // 交换它们的位置
        index = parent;      // index更新
      } else {               // 满足最小堆的特点,break
        break;
      }
    }
  };

  const bubbleDown = (index) => { // 做“下沉”
    while (2 * index + 1 < heap.length) { // 
      let child = 2 * index + 1;
      if (child + 1 < heap.length && freq[heap[child + 1]] < freq[heap[child]]) { // 左右孩子中取较小的去比较
        child++;
      }
      if (freq[heap[index]] > freq[heap[child]]) {
        swap(index, child); // 交换
        index = child;      // 更新 index
      } else { // 如果满足最小堆的属性,break
        break;
      }
    }
  };

  for (const num of uniqueNums) {
    if (heap.length < k) { // heap数组的长度还不够k
      heap.push(num);      // 推入heap数组
      bubbleUp(heap.length - 1); // 执行上浮,频率小的上浮上去
    } else if (freq[num] > freq[heap[0]]) { // 如果当前数字的频次比堆顶数字的频率要大
      heap[0] = num; // 堆顶的数字要更换
      bubbleDown(0); // 然后要做下沉,下沉到合适的位置
    }
  }
  return heap.sort((a, b) => { // 最终它前面是频次少的,最后返回是要逆序回来
    return freq[b] - freq[a];
  });
};