Q22~Q24 栈和队列相关-2

103 阅读3分钟

Q22- code362-敲击计数器

实现思路

方法1: 队列

1 重点是每次 入队/出队列时,都进行超时检测,以优化内存占用

参考文档

01- 直接参考文档

代码实现

1 方法1: 队列 时间复杂度: O(n); 空间复杂度: O(n)

class HitCounter {
  private q: number[]

  constructor() {
    this.q = []
  }

  hit(timestamp: number) {
    this.clearOverTime(timestamp)
    this.q.push(timestamp)
  }

  getHits(timestamp: number) {
    this.clearOverTime(timestamp)
    return this.q.length
  }

  // 移除超过5分钟的 过期敲击数据
  clearOverTime(timestamp: number) {
    while (this.q.length && timestamp - this.q[0] >= 300) {
      this.q.shift()
    }
  }

}

Q23- code155-最小栈

实现思路

方法1: 单栈差值法

1 min: 永远保存的是 当前栈中的最小值

2 stack: 存入的和 当前最小值的差值:diff

3 在单栈差值法中,stack内最多只有一个负数,这是因为:

  • 只有当 val < min 时,diff = val - min 才会是负数
  • 当我们遇到一个负的diff时,会立即更新 min = val
  • 只有当出现比当前min还小的新元素时,才会再次出现负数差值
  • 此时上一个负数差值对应的元素一定还在栈中

每当一个新的负数差值入栈时,它就代表了当前的最小值。 同时,前一个代表最小值的负数差值会失去"最小值"的地位, 但它保存的信息仍然能用于恢复前一个最小值

方法2: [val, minVal]对象法

参考文档

01- 双栈法介绍

02- 方法1直接参考

03- 方法2直接参考

代码实现

1 方法1: 单栈差值法 时间复杂度:O(1) 空间复杂度:O(n)

class MinStack {
  private stack: number[] = [];
  private min: number;

  constructor() {
    this.stack = [];
    this.min = -1
  }

  push(val: number): void {
    // 易错点1:要使用this.stack.length来让min不断初始化
    // 这是因为 可能会存在 入栈又出栈为空再入栈值 的情况
    if (!this.stack.length) {
      this.min = val
    }
    const diff = val - this.min
    // 如果当前元素值比当前最小值还要小,就要更新最小值
    if (diff < 0) {
      this.min = val
    }
    // 存入的是差值
    this.stack.push(diff)
  }

  pop(): void {
    const diff = this.stack.pop()
    // 如果当前出栈值是负数,说明目前出栈的元素 就是最小值
    // 出栈后需要更新min值指向 上一个最小值 oldMin
    if (diff < 0) {
      // 相当于:
      // diff = this.newMin(val) - this.oldMin
      // oldMin = this.newMin - diff && this.min = oldMin
      this.min = this.min - diff
    }
  }

  top(): number {
    let diff = this.stack[this.stack.length - 1];
    // 易错点2:如果diff小于0,说明此时就是最小值,不需要多余操作
    return diff >= 0 ? diff + this.min : this.min;
  }

  getMin(): number {
    return this.min
  }
}

2 方法2: [val, minVal]对象法 时间复杂度:O(1) 空间复杂度:O(n)

class MinStack {
  stack: Array<number[]>
  constructor() {
    this.stack = [[Number.MAX_VALUE, Number.MAX_VALUE]]
  }

  push(val: number): void {
    this.stack.push([val, Math.min(this.getMin(), val)])
  }

  pop(): void {
    this.stack.pop()
  }

  top(): number {
    return this.stack[this.stack.length - 1][0]
  }

  getMin(): number {
    return this.stack[this.stack.length - 1][1]
  }
}

Q24- code232- 用栈实现队列

实现思路

方法1: 入队栈 + 出队栈

1 进入元素时,按序进入 入队栈;

2 弹出元素时,先依次把入队栈元素,【倒序】放入出队栈,再弹出队首元素

3 技巧:可以通过一个front变量,来简化出队栈为空时的 peek操作

参考文档

01- 方法1参考理解

代码实现

1 方法1: 入队栈 + 出队栈 时间复杂度:平均 O(1) 空间复杂度:O(n)


class MyQueue {
  in: number[];
  out: number[];
  front: number;

  constructor() {
    this.in = [];
    this.out = [];
  }

  // O(1)
  push(x: number): void {
    // 通过front记录in的队首值,以简化out为空时的 peek查询
    if (!this.in.length) this.front = x;
    this.in.push(x);
  }

  // 平均 O(1),最坏情况 O(n)
  pop(): number {
    // 倒序把元素都 转移到out栈里
    if (!this.out.length) {
      while (this.in.length) this.out.push(this.in.pop());
    }
    return this.out.pop();
  }

  // O(1)
  peek(): number {
    if (!this.out.length) return this.front;
    return this.out[this.out.length - 1];
  }

  // O(1)
  empty(): boolean {
    return !this.in.length && !this.out.length;
  }
}