leetcode 之栈

169 阅读3分钟

该部分属于没刷过就没思路的题型,因此需要掌握。整体难度一般。

另外,对于 LRUCache 或 栈实现队列 的题目,要把函数形式和参数都背下来,不能只会方法。

20. 有效的括号:先return false

题目20. 有效的括号

for...of 循环遍历

如果是有效括号,那么 「后遇到的左括号要先闭合」,符合栈的进出结构,所以对左括号进行入栈操作;当遇到一个右括号的时候,我们会取出栈顶的左括号与之匹配,如果不是相同类型,返回false

const isValid = function(s) {
  const stack = [];  
  const map = new Map([  // 哈希表
    [')', '('],
    [']', '['],
    ['}', '{']
  ]);
  
  for (let ch of s) {  
    if (map.has(ch)) {  // 遇到右括号时
      // 栈为空 或 栈顶元素不等于对应的左括号时
      if (!stack.length || stack[stack.length - 1] != map.get(ch)) {
        return false;
      }
      stack.pop();
    } else { // 遇到左括号就直接入栈
      stack.push(ch);
    }
  }
  return stack.length == 0;  // 注意!!当s全为左括号时
}

优化:开头增加对s.length % 2 == 1的判断,如果是奇数则一定不是有效括号,因为有效括号长度只能为偶数。

时间复杂度:O(n)。

空间复杂度:O(n + len)。 其中n是栈的长度,len是哈希表的长度,这里等于为6

146. LRU 缓存:Map插入顺序 + keys().next()

题目 146. LRU 缓存

LRU (Least Recently Used,最近最少使用) 缓存淘汰算法:根据数据的历史访问记录来淘汰数据,核心思想是 “如果数据最近被访问过,那么将来被访问的几率也更高” 。因此当某个keyputget操作一旦发生,就认为这个key的记录成了最常使用的,然后就会刷新缓存。

  • Map结构是有序的,相比于传统哈希表,Map实际上是哈希链表,它能够记住键的原始插入顺序。 因此我们可以把key-value按照最后的使用时间排序。

  • Map.prototype.keys()返回一个iterator,一旦 size 超过限度,iterator.next()可以按照顺序获取到最早插入的key,再通过delete删除该key即可。

const map = new Map([['a', 'ab'], ['b', 'bc'], ['c', 'cd']]);
map.keys().next(); // {value: 'a', done: false}
map.keys().next().value; // 'a'
  • 不论get还是put,实际上都是修改在Map中的插入顺序。
const LRUCache = function(capacity) {
  this.count = capacity;
  this.map = new Map();
};

LRUCache.prototype.get = function(key) {
  if (this.map.has(key)) {
    let val = this.map.get(key);
    this.map.delete(key);
    this.map.set(key, val);
    return val;
  } else {
    return -1;
  }
};

LRUCache.prototype.put = function(key, value) {
  if (this.map.has(key)) {
    this.map.delete(key);
  }
  this.map.set(key, value);
  if (this.map.size > this.count) {
    this.map.delete(this.map.keys().next().value); // next()后返回 {value: 键名, done: false }
  }
};

232. 用栈实现队列

题目232. 用栈实现队列

注意:if (!this.inStack)if (!this.stack.length) 结果是相反的。因为 []、{} 逻辑判断时认为是 true。

const MyQueue = function() {
    this.inStack = [];
    this.outStack = [];
};

MyQueue.prototype.push = function(x) {
    this.inStack.push(x);
};

MyQueue.prototype.pop = function() {
    if (!this.outStack.length) {
        this.in2Out();
    }
    return this.outStack.pop(); // 栈顶元素
};

MyQueue.prototype.peek = function() {
    if (!this.outStack.length) {
        this.in2Out();
    }
    return this.outStack[this.outStack.length - 1];
};

MyQueue.prototype.empty = function() { // 判断是否为空
    return this.inStack.length == 0 && this.outStack.length == 0;
};

MyQueue.prototype.in2Out = function() { // 将 inStack 所有元素都 push 到 outStack
    while (this.inStack.length) {
        this.outStack.push(this.inStack.pop());
    }
}

剑指09. 用两个栈实现一个队列

剑指09. 用两个栈实现一个队列

const CQueue = function() {
    this.inStack = [];
    this.outStack = [];
};

CQueue.prototype.appendTail = function(x) {
    this.inStack.push(x);
};

CQueue.prototype.deleteHead = function() {
    if (!this.outStack.length) {
        this.in2out();
    }
    // 多一步判断
    return this.outStack.length ? this.outStack.pop() : -1;
};

CQueue.prototype.in2out = function() {
    while (this.inStack.length) {
        this.outStack.push(this.inStack.pop());
    }
}

时间复杂度: 官方解答是插入和删除操作均为O(1)。个人觉得删除操作O(n)。???

空间复杂度: O(n)。需要使用两个栈存储已有的元素。

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

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

const removeDuplicates = function(s) {
    const stack = [];
    for (let ch of s) {
        if (stack[stack.length - 1] == ch) 
            stack.pop();
        else 
            stack.push(ch);
    }
    
    return stack.join('');
}

时间复杂度:O(n)。

空间复杂度:O(n)。