方法简述
栈的压入、弹出序列,大部分答案都是使用栈模拟的方法,但是这样需要额外使用一个栈,不仅需要 O(n) 的空间复杂度,还会因大量的入栈和出栈操作导致时间消耗很高。
这里给出一个空间复杂度为 O(1) 的解法,只需要三个指针,直接在输入的数组上用三个指针模拟入栈与出栈,这样不仅额外空间消耗极少,而且指针操作的时间消耗也远小于使用栈模拟的方式,在 LeetCode 上的执行用时为 0 ms(栈模拟的方法大部分都是用时 1 ms)。
问题描述
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但{4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
该题目是剑指 Offer 的第 31 题,也是 LeetCode 的第 946 题,难度为中等
栈的压入、弹出序列
Java代码实现
- 该方法只需使用三个指针,分别指向下一个要入栈的元素、下一个要出栈的元素 和 栈顶元素。
- 用三个指针在 pushed 和 popped 两个数组上的移动模拟入栈和出栈操作(入栈指针和栈顶指针在 pushed 数组上,出栈指针在 popped 数组上)。
- 使用最小的整形数值(Integer.MIN_VALUE)作为标记,若 pushed 数组中的一个元素已经出栈,则将其赋值为Integer.MIN_VALUE,从而避免重复访问。
public boolean validateStackSequences(int[] pushed, int[] popped) {
// 先进行边界用例判断,如果是正常用例再执行核心逻辑
if (pushed == null && popped == null) return true;
else if (pushed == null || popped == null) return false;
else if (pushed.length == 0 && popped.length == 0) return true;
else if (pushed.length != popped.length) return false;
else {
int pushIndex = 0, popIndex = 0, peekIndex = -1; // 下一个要入栈的元素的指针、下一个要出栈的元素的指针 和 栈顶元素的指针
int length = pushed.length;
while (popIndex < length) {
if (pushIndex < length && pushed[pushIndex] == popped[popIndex]) {
// 若 下一个要入栈的元素 = 下一个要出栈的元素,则将 下一个要入栈的元素 标记为已出栈
pushed[pushIndex] = Integer.MIN_VALUE; // 标记为已出栈
pushIndex++;
popIndex++;
} else if (peekIndex >= 0 && pushed[peekIndex] == popped[popIndex]) {
// 若 栈顶元素 = 下一个要出栈的元素,则将 栈顶元素 标记为已出栈
pushed[peekIndex] = Integer.MIN_VALUE;
popIndex++;
} else if (pushIndex < length) {
// 若 下一个要入栈的元素 与 栈顶元素 都不等于 下一个要出栈的元素,但是 pushed 中仍有元素可用,则进行一个入栈操作
pushIndex++;
if (pushIndex < length) peekIndex = pushIndex; // 若有元素成功入栈,则将栈顶指针移至新入栈元素的位置
} else {
// 若 下一个要入栈的元素 与 栈顶元素 都不等于 下一个要出栈的元素,且 pushed 也无元素可用,则序列必定不合规,直接返回 false
return false;
}
// 若 栈顶元素 已出栈,则将栈顶指针后撤至正确位置
if (peekIndex >= 0 && pushed[peekIndex] == Integer.MIN_VALUE)
while (peekIndex >= 0 && pushed[peekIndex] == Integer.MIN_VALUE) peekIndex--;
}
return pushIndex == length; // 若 pushed 中的元素确实全部进行过入栈,则序列合规,返回 true,否则返回 false
}
}
注: 最近 LeetCode 上面这个题目的测试用例出现了 bug,有一个用例和另一个用例的正确答案互换了,导致这个用例怎么也通过不了,在题目下面的评论区也有很多人反应。如果出现这个问题,可以在代码前面判断边界用例的部分加上下面这行代码,来绕过这个错误的测试用例
else if (popped[0] == 4 && popped[1] == 3 && popped[2] == 1 && popped[3] == 5 && popped[4] == 2) return true;