skip to content
WNLee's Blog

数据结构之单调栈

/ 6 min read

单调栈是在数据结构`栈`的基础上加数据特性而得到的新数据结构...

单调栈

概念

单调栈是一种数据结构,在栈结构上增加了数据特征,数据特征分别有单调递增性和单调递减性。单调递增栈即栈中的元素或者关联属性保持递增。单调递减栈即栈中的元素或者关联属性保持递减。

实现


// 重点:单调栈保持单调性的操作是在入栈时与栈顶比较,不符合条件则将栈顶出栈直到满足条件再入栈
class MonotoneStack<T> {
  constructor() {}

  public data: T[] = [];

  get top() {
    return this.data.length;
  }
  get isEmpty() {
    return this.top === 0;
  }
  get topElement() {
    if (!this.top) return undefined;
    return this.data[this.top - 1];
  }

  pop() {
    return this.data.pop();
  }
  push(elem: T, ...args: any[]) {
    throw 'Must implement `push` method!';
  }
  clear() {
    this.data.length = 0;
    return this.data;
  }
}

// 单调递增
class MonotoneIncreaseStack extends MonotoneStack<number> { 
  constructor() { super() }

  push(elem: number, iterator: (elem: number) => void = function() {}) {
    while(this.data.length && this.topElemen > elem) {
      iterator.call(this, this.data.pop());
    }

    this.data.push(elem);
  }
}

// 单调递减
class MonotoneDecreaseStack extends MonotoneStack<number> { 
  constructor() { super() }

  push(elem: number, iterator: (elem: number) => void = function() {}) {
    while(this.data.length && this.topElemen < elem) {
      iterator.call(this, this.data.pop());
    }

    this.data.push(elem);
  }
}

特性

  1. 栈的基本属性,后进先出(LIFO,last-in first-out);
  2. 栈里的元素(或者元素关联属性)具备单调性,递增或递减;
  3. 假设 f 为求出离 i 最近比自己大的数或者自己小的数,f(i)总是在 i-1, f(i-1), f(f(i-1))…中取值;
    • 即,当 i 入栈之前栈中的数据满足如下规律,i-1, f(i-1), f(f(i-1))…;
  4. 假定一个区间入栈到单调栈中,栈中元素具备区间极值到右边区间的单调性;
    • 将区间逐个入栈到单调递减栈中,栈中元素为满足区间最大值到区间最右值逐个递减;
    • 将区间逐个入栈到单调递增栈中,栈中元素为满足区间最小值到区间最右值逐个递增;

补充说明

从图片归纳 3、4 点特性,假设有一串正整数 3 10 5 7 3 2 6 4 0.5 1 5 7,1~10已入单调递减栈栈,先准备入栈的是 11

图中 11 左边距离最小且比自己小的值,在图中连线的元素查找,即 10, f(10) = 8, f(8) = 7, f(7) = 4, f(4) = 2,经过比较 7 的值比 11 大且距离最近。已入栈的区间为 2~10,从图中可以看出栈中保留了元素使 2 4 7 8 10,2为最大值并递减到1。

1

图中11入栈时会把 8 9 10 弹出,新的栈中元素连线如下。

2

应用

1. 柱状图最大矩形面积

  1. 题目链接 ref
  2. 代码实现
class MonotoneStack<T> {
  constructor() {}

  public data: T[] = [];

  get top() {
    return this.data.length;	
  }
  get isEmpty() {
    return this.top === 0;
  }
  get topElement() {
    if (!this.top) return undefined;
    return this.data[this.top - 1];
  }

  pop() {
    return this.data.pop();
  }
  push(elem: T, ...args: any[]) {
    throw 'Must implement `push` method!';
  }
  clear() {
    this.data.length = 0;
    return this.data;
  }
}

class MonotoneIncreaseStack extends MonotoneStack<number> { 
  constructor(source: number[]) { 
    super(); 
    this.source = source;
  }

  public source: number[]

  push(elem: number, iterator: (elem: number) => void = function() {}) {
    while(this.data.length && this.source[this.topElement] > this.source[elem]) {
      iterator.call(this, this.data.pop());
    }

    this.data.push(elem);
  }
}

class LargestRectangleArea extends MonotoneIncreaseStack {
  constructor(heights: number[]) {
    super(heights);

    this.source = this.source.slice(0);
    this.source.unshift(0);
    this.source.push(0);
  }

  run() {
    return this.source.reduce((acc, elem, i) => {
      this.push(i, function(cur) {
        acc = Math.max(acc, (i - this.topElement - 1) * this.source[cur]);
      });
      return acc;
    }, 0);
  }
}

function largestRectangleArea(heights: number[]): number {
  return (new LargestRectangleArea(heights)).run();
};

2. 接雨水

  1. 题目链接 ref
  2. 代码实现
class MonoStack<T> {
  constructor() {}

  public data: T[] = [];

  get top() {
    return this.data.length;	
  }
  get isEmpty() {
    return this.top === 0;
  }
  get topElement() {
    if (!this.top) return undefined;
    return this.data[this.top - 1];
  }

  pop() {
    return this.data.pop();
  }
  push(elem: T, ...args: any[]) {
    throw 'Must implement `push` method!';
  }
  clear() {
    this.data.length = 0;
    return this.data;
  }
}

class MonoDecreaseStack extends MonoStack<number> { 
  constructor(source: number[]) { 
    super(); 
    this.source = source;
  }

  public source: number[]

  push(elem: number, iterator: (elem: number) => void = function() {}) {
    while(this.data.length && this.source[this.topElement] < this.source[elem]) {
      iterator.call(this, this.data.pop());
    }

    this.data.push(elem);
  }
}

function trap(height: number[]): number {
  const stk = new MonoDecreaseStack(height);

  return height.reduce((acc, elem, i) => {
    stk.push(i, function(idx) {
        if (this.topElement !== undefined) {
          const top = height[this.topElement];
          const cur = height[idx];
          if (top !== cur) {
            const dis = i - this.topElement - 1;
            const depth = Math.min(elem, top) - cur;
            acc += dis * depth;
          }
        }
    });
    return acc;
  }, 0);
};

题解说明

找了两篇详细题解可以参考

  1. 单调栈 —— 面试热门知识点画圈圈
  2. 单调栈学习与应用