【前端er每日算法】队列应用--239滑动窗口最大值347. 前 K 个高频元素

65 阅读2分钟

题目一 239. 滑动窗口最大值

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值 。

思路

  1. 构造单调递减队列,主要有3个操作:
    • push:如果当前队列的最后的元素小于当前值,元素出队,直到没有小的元素,这样队列保持单调递减
    • pop:带参数,表示要出队的val,如果val等于队头元素,才需要操作出队,其他不需要,因为比队头小的元素都已经出队列了
    • front:仅返回队头元素,不删除
  2. 用构造好的队列模拟滑动窗口移动
/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
class MyQueue {
    queue = [];
    // 出栈:只有元素等于最左边的元素才出栈,否则不需要操作
    pop(value) {
        const queue = this.queue;
        const top = this.front();
        if (queue.length && value === top) {
            queue.shift();
        }
    }
    // 入队:如果当前元素大于队列中的元素,则将队列中元素出队,直到队列变为单调递减
    push(value) {
        const queue = this.queue;
        while (queue.length && value > queue[queue.length - 1]) {
            queue.pop();
        }
        queue.push(value);
    }
    // 取第一个元素
    front() {
        const top = this.queue[0];
        return top;
    }
}

var maxSlidingWindow = function(nums, k) {
    const queue = new MyQueue();
    const result = [];
    // 先构造k个大小的队列
    for (let i = 0; i < k; i++) {
        queue.push(nums[i]);
    }
    // 把第一个放进去
    result.push(queue.front())
    // 遍历后面的元素,不断的出对左边的元素,入队当前元素,模拟滑动窗口
    for (let i = k; i < nums.length; i++) {
        queue.pop(nums[i-k]);
        queue.push(nums[i]);
        result.push(queue.front());
    }
    return result;
};

题目二 347. 前 K 个高频元素

思路

  1. 先统计每个元素出现的频率。
  2. 按照频率的大小构造小顶堆,这样最后留的k个元素就是高频的k元素,其他小的都出队了,小顶堆的保存的是value,比较大小的话,需要从map中根据key去取。
  3. 最小堆第0位保留,从第一位开始放置元素,这样索引方便计算。
  4. 构造小顶堆的过程
    • 取map的前k个元素先构造小顶堆,从第一个非叶子节点开始进行节点调整,每个点依次调整为小顶堆

    • k后面的数字,每次和堆顶元素进行比较

      • 如果小于堆顶元素,不做处理,
      • 大于堆顶元素,则替换堆顶元素,并且将它调整为符合小顶堆的特性
    • 调整元素

      • 记录当前节点和左右孩子节点的最小索引
      • 如果左孩子节点有效,且小于父亲节点,则更新为左孩子节点的索引
      • 如果右孩子有效,且小于最小节点,则更新最小索引为右孩子节点
      • 如果当前索引和父亲节点不相等,则进行调整,如果调整完了,再对minIndex节点进行递归调整。
var topKFrequent = function(nums, k) {
    const map = {};
    const len = nums.length;
    for (let i = 0; i < len; i++) {
        if (map[nums[i]]) {
            map[nums[i]]++;
        } else {
            map[nums[i]] = 1;
        }
    }
    const heap = [0];
    let count = 0;
    // 收集map的前k个元素构造小顶堆,heap里存的是key
    Object.keys(map).forEach((key) => {
        if (count++ < k) {
            heap.push(key);
            if (count === k) {
                buildHeap(heap, map, k);
            }
        }  else if (map[key] > map[heap[1]]) {
            heap[1] = key;
            heapify(heap, map, 1);
        }
    })
    return heap.slice(1);
};

function buildHeap(heap, map, k) {
    const start = Math.floor(k / 2);
    for (let i = start; i > 0; i--) {
        heapify(heap, map, i);
    }
}

function heapify(heap, map, i) {
    const len = heap.length;
    let minIndex = i;
    const leftChild = 2 * i;
    if (leftChild <= len && map[heap[leftChild]] < map[heap[i]]) {
        minIndex = leftChild;;
    }
    const rightChild = leftChild + 1;
    if (rightChild <= len && map[heap[rightChild]] < map[heap[minIndex]]) {
        minIndex = rightChild;
    }
    // 如果堆顶元素大于孩子节点,则和孩子节点进行交换
    if (minIndex !== i) {
        [heap[i], heap[minIndex]] = [heap[minIndex], heap[i]];
        if (minIndex < len) {
            heapify(heap, map, minIndex);
        }
    }
}