左神算法学习笔记 - 015【入门】栈的入门题目-最小栈

9

【算法讲解015【入门】栈的入门题目-最小栈】 https://www.bilibili.com/video/BV15X4y177cM/?share_source=copy_web&vd_source=939a5033dc1ec4f712d59a4a4042e169

leetcode

这道题所谓最小栈,实际上是希望在普通栈操作的基础上,提供一个「得到栈中最小值」的方法,能够在任何时候调用它得到当前栈的最小值。

解法是非常精妙的。


如评论区有人留言所说:算法做多了感觉就是时间换空间。

这道题如果笨办法做的话,确实时间复杂度比较低:

  • 左神简单描述了一下,比如用遍历来解,那就是每次找 min 的时候遍历一下栈,然后下次弹出的话再次遍历,显然不是最优解。而且我觉得遍历栈也是比较麻烦的事。

  • 另外,我想到,既然是需要「任何时候得到一个集合的最小值」,可以使用小根堆,在操作栈的同时,同步操作小根堆就可以了。这样可以通过,但是时间复杂度不好。

左神讲解的方法比较精妙,大概意思如下:

  • 在数据栈之外,同步维护一个最小栈,这个最小栈的栈顶用来代表「当前数据状况下,数据栈中的最小值」

  • 如何维护这个最小栈是关键

    • 当插入数据时,数据栈插入,最小栈对比「新数据」和「栈顶数据」,如果新数据更小则插入,如果不是更小,重复插入一遍栈顶元素(意味着它还是最小的那个)

    • 当弹出数据的时候,数据栈和最小栈同步弹出

也就是通过额外的一个栈 + 一个精妙的规则,让我们方便地得到了 getMin() 这个功能。

使用 Stack 类实现的代码:

class MinStack1 {
		public Stack<Integer> data;
		public Stack<Integer> min;

		public MinStack1() {
			data = new Stack<Integer>();
			min = new Stack<Integer>();
		}

		public void push(int val) {
			data.push(val);
			if (min.isEmpty() || val <= min.peek()) {
				min.push(val);
			} else { // !min.isEmpty() && val > min.peek()
				min.push(min.peek());
			}
		}

		public void pop() {
			data.pop();
			min.pop();
		}

		public int top() {
			return data.peek();
		}

		public int getMin() {
			return min.peek();
		}
	}

当然,使用语言自己的栈,还是相对较慢,对于栈来说,我们可以根据数据量自己用数组 + size 变量实现:

class MinStack2 {
		// leetcode的数据在测试时,同时在栈里的数据不超过这个值
		// 这是几次提交实验出来的,哈哈
		// 如果leetcode补测试数据了,超过这个量导致出错,就调大
		public final int MAXN = 8001;

		public int[] data;
		public int[] min;
		int size;

		public MinStack2() {
			data = new int[MAXN];
			min = new int[MAXN];
			size = 0;
		}

		public void push(int val) {
			data[size] = val;
			if (size == 0 || val <= min[size - 1]) {
				min[size] = val;
			} else {
				min[size] = min[size - 1];
			}
			size++;
		}

		public void pop() {
			size--;
		}

		public int top() {
			return data[size - 1];
		}

		public int getMin() {
			return min[size - 1];
		}
	}
  • 注意 size - 1 这种操作