leetcode——栈和队列系列


theme: smartblue

232. 用栈实现队列

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):
实现 MyQueue 类:
void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
boolean empty() 如果队列为空,返回 true ;否则,返回 false

数组对象的push与pop方法分别在数组的尾部添加与删除元素。push方法有一个参数,也就是要添加到数组尾部的元素,而pop方法则没有参数,而是返回从数组尾部删除的元素。
s1用来进队列,s2用来出队列,看队列是不是空就要看s1和s2两个栈元素个数
注意:push元素是先存在s1中,只有s2全部走光之后,s1才会把存的元素放进s2中
先耗尽s2,再耗s1

var MyQueue = function() {
    // 输入栈用于输入数据
    this.s1 = []
    // 要先进先出,输入到输出
    this.s2 = []
};

/** 
 * @param {number} x
 * @return {void}
 */
MyQueue.prototype.push = function(x) {
    // 进队列都是进s1栈
    this.s1.push(x)
};

/**
 * @return {number}
 */
MyQueue.prototype.pop = function() {
    const len = this.s2.length
    // 判断s2栈是有元素,有的话直接弹出最后一个
    if(len){
        return this.s2.pop();
    }
    // 将s1中的元素全部倒入s2中
    while(this.s1.length){
        this.s2.push(this.s1.pop())
    }
    return this.s2.pop();
};

/**
 * @return {number}
 */
MyQueue.prototype.peek = function() {
    const x = this.pop();//查看队头的元素 复用pop方法,然后在让元素push进输出栈
    this.s2.push(x);// 重新在push进去,因为这个只是查看
    return x;
};

/**
 * @return {boolean}
 */
MyQueue.prototype.empty = function() {
    return !this.s1.length && !this.s2.length
};

225. 用队列实现栈

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。

实现 MyStack 类:
void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。

记住!队列只有shift和push用法!!s1存储新进来的元素,如果s1空了,说明s2是现在所有的元素集合,如果s1没空,就把s1的前面放在s2的后面
先耗尽s1,再耗尽s2

var MyStack = function() {
    this.queue1 = [];
    this.queue2 = [];
};

/**
 * Push element x onto stack. 
 * @param {number} x
 * @return {void}
 */
MyStack.prototype.push = function(x) {
    this.queue1.push(x);
};

/**
 * Removes the element on top of the stack and returns that element.
 * @return {number}
 */
MyStack.prototype.pop = function() {
    // 减少两个队列交换的次数, 只有当queue1为空时,交换两个队列
    if(!this.queue1.length) {
        [this.queue1, this.queue2] = [this.queue2, this.queue1];
    }
    while(this.queue1.length > 1) {
        this.queue2.push(this.queue1.shift());
    }
    return this.queue1.shift();
};

/**
 * Get the top element.
 * @return {number}
 */
MyStack.prototype.top = function() {
    const x = this.pop();
    this.queue1.push(x);
    return x;
};

/**
 * Returns whether the stack is empty.
 * @return {boolean}
 */
MyStack.prototype.empty = function() {
    return !this.queue1.length && !this.queue2.length;
};

20. 有效的括号

给定一个只包括 `'('`,`')'`,`'{'`,`'}'`,`'['`,`']'` 的字符串 `s` ,判断字符串是否有效。
输入: s = "()"
输出: true

这道题目没那么复杂,题目就说了左括号必须以正确的顺序闭合,其次就是第一个必定是左括号。

var isValid = function(s) {
    // 清除所有空格    
    let str = s.split(' ').join('')
    //  如果全为空字符串则返回true   
    if(str === ''){
       return true
    }
    let len = s.split(' ').join('').length
    // 如果是奇数并且是]})开头则返回false
    if(len % 2 !== 0 || str[0] === ')' || str[0] === '}' || str[0] === ']'){
       return false
    }
    // 创建一个栈
    let stack = []
    let current = null
    let top = str[0]
    let map = {
      ')': '(',
      ']': '[',
      '}': '{'
    }
    for(let i = 0;i<str.length;i++){
      current = str[i]
      top = stack[stack.length-1] || null
      // 如果栈顶元素和当前遍历元素相同则出栈否则入栈
      if(map[current] === top){
        stack.pop()
      }else{
        stack.push(current)
      }
    }
    return stack.length === 0
};

1047. 删除字符串中的所有相邻重复项

给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
在 S 上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
输入:"abbaca"
输出:"ca"
解释:
例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,
这是此时唯一可以执行删除操作的重复项。
之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。

特别简单,就是把字母一个个放进栈stack中,如果准备放进去的和栈顶的相同就弹出否则继续放

var removeDuplicates = function(s) {
    const stack = [];
    for(const x of s){
        if(stack[stack.length-1] !== x || stack.length === 0){
            stack.push(x);
        }else if(stack[stack.length-1] === x){
            stack.pop();
        }
    }
    return stack.join("")
};

150. 逆波兰表达式求值

有效的算符包括 `+`、`-`、`*`、`/` 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
注意 两个整数之间的除法只保留整数部分。
输入: tokens = ["2","1","+","3","*"]
输出: 9
解释: 该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9

二叉树中的后序遍历

var evalRPN = function(tokens) {
    const s = new Map([
        ["+", (a, b) => a * 1  + b * 1],
        ["-", (a, b) => b - a],
        ["*", (a, b) => b * a],
        ["/", (a, b) => (b / a) | 0]
    ]);
    const stack = [];
    for (const i of tokens) {
        if(!s.has(i)) {
            stack.push(i);
            continue;
        }
        stack.push(s.get(i)(stack.pop(),stack.pop()))
    }
    return stack.pop();
};

239. 滑动窗口最大值

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。
你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

方法1:优先队列:运用树的知识先不看
方法2:单调队列
bz.png
1.如果后面进入的元素比队列的元素大,那就队列先从队尾丢弃头元素再把新元素push进去(while循环删掉所有比他小的)
2.如果后面的元素比队列最后的一个元素大,直接push进去。
3.q[0]<=i-k (举例)表示前三个已经遍历结束开始框第3个以后的元素,如果前三个里面有特别大的元素后面的元素没资格踢掉他就用这个判断条件。(反正就是来解决所有踢不掉的元素)

  1. i=2的时候表示前3个都已经比较过也找出了最大值,后面每比较一个就放一个。\
var maxSlidingWindow = function (nums, k) {
    const q = [];//单递减的双端队列
    const ans = [];//最后的返回结果
    for (let i = 0; i < nums.length; i++) {//循环nums
        //当进入滑动窗口的元素大于等于队尾的元素时 不断从队尾出队,
        //直到进入滑动窗口的元素小于队尾的元素,以保证单调递减的性质
        while (q.length && nums[i] >= nums[q[q.length - 1]]) {
            q.pop();
        }
        q.push(i);//元素的索引入队
        while (q[0] <= i - k) {//队头元素已经在滑动窗口外了,移除对头元素
            q.shift();
        }
        //当i大于等于k-1的时候,单调递减队头就是滑动窗口的最大值
        if (i >= k - 1) ans.push(nums[q[0]]);
    }
    return ans;
};

347. 前 K 个高频元素

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

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

第一种方法map

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
var topKFrequent = function(nums, k) {
    const map = new Map()
    nums.forEach(n => {
        map.set(n, map.has(n) ? map.get(n)+1 : 1)
    })
    // 首先将字典转成数组,然后对数组中的第二个元素(频度)从大到小排序
    const list = Array.from(map).sort((a, b) => b[1] - a[1])
    // 截取频率前k高的元素
    return list.slice(0, k).map(n => n[0])
};

注意!!

Map: { 1 => 3, 2 => 2, 3 => 1 }
Array.from(map):[ [ 1, 3 ], [ 2, 2 ], [ 3, 1 ] ]

第二种方法:优先级队列 就是一个披着队列外衣的堆
把一个完全二叉树通过层序遍历存储到数组当中,这个数组就叫做堆。


大根堆:所有结点的值都大于其子结点的值,我们称为大根堆。
小根堆:所有结点的值都小于其子结点的值,我们称为小根堆。
优先队列使用的是一个小堆。

//创建小顶堆 
const priorityQueue = new Heap((a, b) => a[1] - b[1]);

在这里插入图片描述

class Heap {
    constructor(comparator = (a, b) => a - b, data = []) {
        this.data = data;
        this.comparator = comparator;//比较器
        this.heapify();//堆化
    }

    heapify() {
        if (this.size() < 2) return;
        for (let i = Math.floor(this.size()/2)-1; i >= 0; i--) {
            this.bubbleDown(i);//bubbleDown操作
        }
    }

    peek() {
        if (this.size() === 0) return null;
        return this.data[0];//查看堆顶
    }

    offer(value) {
        this.data.push(value);//加入数组
        this.bubbleUp(this.size() - 1);//调整加入的元素在小顶堆中的位置
    }

    poll() {
        if (this.size() === 0) {
            return null;
        }
        const result = this.data[0];
        const last = this.data.pop();
        if (this.size() !== 0) {
            this.data[0] = last;//交换第一个元素和最后一个元素
            this.bubbleDown(0);//bubbleDown操作
        }
        return result;
    }

    bubbleUp(index) {
        while (index > 0) {
            const parentIndex = (index - 1) >> 1;//父节点的位置
            //如果当前元素比父节点的元素小,就交换当前节点和父节点的位置
            if (this.comparator(this.data[index], this.data[parentIndex]) < 0) {
                this.swap(index, parentIndex);//交换自己和父节点的位置
                index = parentIndex;//不断向上取父节点进行比较
            } else {
                break;//如果当前元素比父节点的元素大,不需要处理
            }
        }
    }

    bubbleDown(index) {
        const lastIndex = this.size() - 1;//最后一个节点的位置
        while (true) {
            const leftIndex = index * 2 + 1;//左节点的位置
            const rightIndex = index * 2 + 2;//右节点的位置
            let findIndex = index;//bubbleDown节点的位置
            //找出左右节点中value小的节点
            if (
                leftIndex <= lastIndex &&
                this.comparator(this.data[leftIndex], this.data[findIndex]) < 0
            ) {
                findIndex = leftIndex;
            }
            if (
                rightIndex <= lastIndex &&
                this.comparator(this.data[rightIndex], this.data[findIndex]) < 0
            ) {
                findIndex = rightIndex;
            }
            if (index !== findIndex) {
                this.swap(index, findIndex);//交换当前元素和左右节点中value小的
                index = findIndex;
            } else {
                break;
            }
        }
    }

    swap(index1, index2) {//交换堆中两个元素的位置
        [this.data[index1], this.data[index2]] = [this.data[index2], this.data[index1]];
    }

    size() {
        return this.data.length;
    }
}

var topKFrequent = function (nums, k) {
    const map = new Map();

    for (const num of nums) {//统计频次
        map.set(num, (map.get(num) || 0) + 1);
    }

    //创建小顶堆
    const priorityQueue = new Heap((a, b) => a[1] - b[1]);

    //entry 是一个长度为2的数组,0位置存储key,1位置存储value
    for (const entry of map.entries()) {
        priorityQueue.offer(entry);//加入堆
        if (priorityQueue.size() > k) {//堆的size超过k时,出堆
            priorityQueue.poll();
        }
    }

    const ret = [];

    for (let i = priorityQueue.size() - 1; i >= 0; i--) {//取出前k大的数
        ret[i] = priorityQueue.poll()[0];
    }

    return ret;
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值