打卡-算法训练营-Day11 | 150. 逆波兰表达式求值;239. 滑动窗口最大值;347.前 K 个高频元素

61 阅读3分钟

逆波兰表达式求值

leetcode链接:leetcode.cn/problems/ev…

向零截断的意思是:

对于小于0的整数,向上取整,比如,-1.15,向零截断是-1;

对于大于0的整数,向下取整,比如,1.15,向零截断是1;

在JS中,不能统一的用Math.floor函数处理,Math.floor(-1.15)结果是-2

应该要能很快想到使用栈来做题,因为有点类似消消乐

本题目思路:

  1. 遇到数字就入栈
  2. 遇到运算法,则取出末尾两个数字做运算
  3. 新结果入栈
  4. 重复,直到遍历完所有元素
  5. 最后栈中的元素就是结果
var evalRPN = function(tokens) {
  let stack = [];
  let size = tokens.length;

  for (let i = 0; i < size; i++) {
    if (Number.isNaN(Number(tokens[i]))) {
      // 非数字的运算符
      let result;
      let n2 = stack.pop();
      let n1 = stack.pop();
      switch (tokens[i]) {
        case "+":
          result = n1 + n2;
          break;
        case "-":
          result = n1 - n2;
          break;
        case "*":
          result = n1 * n2;
          break;
        case "/":
          result = n1 / n2;
          if (result < 0) {
            result = Math.ceil(result);
          } else {
            result = Math.floor(result);
          }
          break;
      }
      // 新结果入栈
      stack.push(result);
    } else {
      // 数字入栈
      stack.push(Number(tokens[i]));
    }
  }
  return stack.pop();
};

滑动窗口最大值

leetcode链接:leetcode.cn/problems/sl…

暴力法的时间复杂度是O(n × k),会超出时间限制,这道题目有难度,一刷先理解思路

需要构造一个单调递减队列,队列出口始终是最大值,每次取出队列出口元素即可

思路:

  1. 前k个元素入队列,入队列的时候要保证队列元素顺序是递减的,不符合的元素要pop
  2. 取出当前窗口的最大值
  3. 遍历剩余元素,每次入队列一个元素的同时队列也要出去一个元素(移动窗口位置),出队列的时候需要判断一下是不是最大值,如果是的则直接移除,不是则不进行任何操作
  4. 重复2-3步
// 构造单调递减队列
var Myqueue = function() {
    this.queue = [];
}

Myqueue.prototype.enqueue = function(value) {
    if (this.queue.length) {
      let back = this.queue[this.queue.length - 1];
      while(back !== undefined && value > back) {
        this.queue.pop();
        back = this.queue[this.queue.length - 1];
      }
      this.queue.push(value);
    } else {
        // 队列为空,直接入栈
        this.queue.push(value);
    }
}

Myqueue.prototype.dequeue = function(value) {
    let peek = this.getMaxValue();
    if (peek === value) {
        this.queue.shift();
    }
}

Myqueue.prototype.getMaxValue = function() {
    return this.queue[0];
}

var maxSlidingWindow = function(nums, k) {
    let size = nums.length;
    let result = [];
    let queue = new Myqueue();

    // 先将k个元素放入队列中,
    let i = 0;
    while (i < k) {
        queue.enqueue(nums[i]);
        i++;
    }

    // 获取当前队列的最大值
    result.push(queue.getMaxValue());

    let j = 0;
    while (i < size) {
        // 移动窗口位置,获取最大值
        queue.enqueue(nums[i]);
        queue.dequeue(nums[j]);
        result.push(queue.getMaxValue());
        i++;
        j++;
    }
    return result;
};

前 K 个高频元素

leetcode链接:leetcode.cn/problems/to…

统计出现的频率,一般都是用哈希法的map数据结构

思路:哈希表中的key保存元素,value是该元素出现的频率,根据value对map进行排序,取出前k个元素的key值

JS中有两种方式实现map,一种是对象,另一种是Map数据结构(卡哥讲的小顶堆有点复杂了,JS中需要手写实现堆)

使用对象作为哈希表存储,需要借助数组的排序函数,整体处理起来比较繁琐,不过思路比较清晰

var topKFrequent = function(nums, k) {
    let result = [];
    let objArray = [];
    let map = {};

    // 统计元素出现的频率
    for (let i = 0; i < nums.length; i++) {
        if (map[nums[i]]) {
            map[nums[i]]++;
        } else {
            map[nums[i]] = 1;
        }
    }

    // 以key和value的对象形式存储在数组中
    for (let key in map) {
        let temp = {
            key: key,
            value: map[key]
        }
        objArray.push(temp);
    }

    // 根据value属性进行排序
    objArray.sort((a, b) => b.value - a.value);
    
    // 取出前k个元素的key值
    for (let i = 0; i < k; i++) {
        result.push(Number(objArray[i].key));
    }

    return result;
};

也可以使用Map数据结构,学习下entries函数的用法

var topKFrequent = function(nums, k) {
    let map = new Map();
    for (let num of nums) {
        if (map.has(num)) {
            map.set(num, map.get(num) + 1);
        } else {
            map.set(num, 1);
        }
    }
    return [...map.entries()]
        .sort((a, b) => b[1] - a[1])
        .slice(0, k)
        .map(i => i[0]);
};