举例分析
- 与上两篇问中画图方法一样,我们可以用举例模拟的方法思考分析复杂问题。当一眼不能看出问题的规律的时候,我们可以用几个具体的例子来模拟一下问题的过程。这样就和我们在程序出现问题时候的debug一样,走一下整个流程,可以直观的看到整个过程。
- 具体的例子还能帮助我们确保代码的质量,在编码完成后,可以将例子当做测试用例来模拟运行,看每一步操作后的结果和我们预期的是不是一样的。
包含min方法的栈
- 题目:定义栈的数据结构,请在改类型中实现一个能够得到栈的最小元素min函数。在改栈中,调用min,push,pop的时间复杂度O(1)
- 此处与普通栈不同点在于需要知道每次栈变动时候最小值。并且难点在于O(1)的时间复杂度,我们第一反应是标记最小值,这样可以在O(1)时间得到最小元素。但是最小值出栈后,次小的值就变成最小值,此时是无法获取这个值
- 另外一个思路,每次入栈对栈中元素进行排序,这样能拿到最小值在栈首,会在尾。但是这样就违背了栈的后进先出的原则就不是栈了。
- 分析到此处发现一个栈A并不能解决问题,我们用一个辅助的栈空间B,每次添加一个元素到A时候,将添加元素与最小元素比较(临时变量保存最小元素),将最小元素添加到B,即使最小元素没有变化仍然重复添加到B占位用。我们用如下案例分析:
步骤 | 操作 | 数据栈A | 辅助栈B | 最小值 |
---|---|---|---|---|
1 | 压入3 | 3 | 3 | 3 |
2 | 压入6 | 3,6 | 3,3 | 3 |
3 | 压入4 | 3,6,4 | 3,3,3 | 3 |
4 | 压入45 | 3,6,4,45 | 3,3,3,3 | 3 |
5 | 压入0 | 3,6,4,45,0 | 3,3,3,3,0 | 0 |
6 | 压入2 | 3,6,4,45,0,2 | 3,3,3,3,0,0 | 0 |
-
如上表格,我们将最小元素每次都添加到辅助栈B中,就能保证辅助栈的栈顶是最小元素。
-
当最小元素从数据栈弹窗,我们同时操作辅助栈弹窗,这样保证辅助栈下一个值是最小值。
-
每一步操作数据栈,辅助站都同步,可以保证辅助栈顶永远都是数据栈中所有数据的最小值
-
实现方法是在之前文章数据结构与算法–简单栈实现及其应用基础上完成。实现如下:
/**
* 包含min方法的栈实现
* @author liaojiamin
* @Date:Created in 16:02 2021/4/2
*/
public class MyStackWithMin {
private static MyStack dataStack = new MyStack();
private static MyStack minStack = new MyStack();
public static void push(int num){
if(dataStack.size() == 0 && minStack.size() == 0){
dataStack.push(num);
minStack.push(num);
}else {
dataStack.push(num);
if((int)minStack.getTop() > num){
minStack.push(num);
}else {
minStack.push(minStack.getTop());
}
}
}
public static int pop(){
if(dataStack.size() == 0 || minStack.size() == 0){
return Integer.MAX_VALUE;
}
minStack.pop();
return (int)dataStack.pop();
}
public static int min(){
if(minStack.size() == 0){
return Integer.MAX_VALUE;
}
return (int)minStack.pop();
}
public static void main(String[] args) {
Random random = new Random();
for (int i = 0; i < 100; i++) {
int temp = random.nextInt(100);
System.out.println(temp);
push(temp);
}
System.out.println(min());
}
}
栈的压入,弹出序列
- 题目:输入两个整数序列,第一个序列表示栈的压入顺序,判断第二个序列是否可能是栈的弹出顺序,假设入站的所有数字均不相等,例如:1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是可能的一个出栈,但是4,3,5,1,2就不可能是出栈的方式
- 我们依然用案例的方法分析,如下表格分析题中两种情况。
步骤 | 操作 | 栈 | 弹出数字 |
---|---|---|---|
1 | 压入1 | 1 | |
2 | 压入2 | 1,2 | |
3 | 压入3 | 1,2,3 | |
4 | 压入4 | 1,2,3,4 | |
5 | 弹出 | 1,2,3 | 4 |
6 | 压入5 | 1,2,3,5 | |
7 | 弹出 | 1,2,3 | 5 |
8 | 弹出 | 1,2 | 3 |
9 | 弹出 | 1 | 2 |
10 | 弹出 | 1 |
- 如上表格中每一个步骤操作,我们在第五步骤时候,弹出4,压入5,之后持续弹出,就能得到对应的弹出序列。
- 我们用同样的方式来递推第二个序列
步骤 | 操作 | 栈 | 弹出数字 |
---|---|---|---|
1 | 压入1 | 1 | |
2 | 压入2 | 1,2 | |
3 | 压入3 | 1,2,3 | |
4 | 压入4 | 1,2,3,4 | |
5 | 弹出 | 1,2,3 | 4 |
6 | 弹出 | 1,2 | 3 |
7 | 压入5 | 1,2,5 | |
8 | 弹出 | 1,2 | 5 |
9 | 下一个弹出的是1,但是1 不在栈顶,压栈序列已经都入栈,操作无法继续 |
- 如上两表格分析,入栈,出栈过程,我们可以判断一个序列是不是栈的弹出序列有如下规律:
- 如果下一个弹出的数字正好是栈顶数字,那么直接弹出
- 如果下一个弹出的数字不在栈顶,我们吧压栈序列中还没有入栈的数字压入辅助栈
- 持续压入直到下一个需要弹出的数字压入栈顶位置
- 如果所有数字都压入了栈但是还没找到下一个弹出的数字,那么该序列不存在一个弹出序列。
- 综上有如下实现:
/**
* 存在栈A的入栈系列S,判断给出的序列B是否可能是A 的出序列,例如1,2,3,4,5 入栈,4,5,3,2,1 出栈
* @author liaojiamin
* @Date:Created in 16:54 2021/4/2
*/
public class ValidateIsPopOrder {
public static boolean validateIsPopOrder(int[] orderPush, int[] orderPop){
if(orderPush == null || orderPop == null){
return false;
}
if(orderPush.length != orderPop.length){
return false;
}
MyStack myStack = new MyStack();
int length = orderPop.length;
int pushPosition = 0;
int popPosition = 0;
while (pushPosition < length || popPosition < length){
while (myStack.size() > 0 && (int)myStack.getTop() == orderPop[popPosition] && popPosition < length){
myStack.pop();
popPosition++;
}
if(pushPosition < length){
myStack.push(orderPush[pushPosition]);
pushPosition ++;
}
if(pushPosition == length && myStack != null && (int)myStack.getTop() != orderPop[popPosition]){
return false;
}
}
return !(myStack.size() != 0);
}
public static void main(String[] args) {
int[] push = {1,2,3,4,5};
// int[] pop = {4,5,3,2,1};
// int[] pop = {3,2,1,5,4};
int[] pop = {4,3,5,1,2};
System.out.println(validateIsPopOrder(push, pop));
}
}
- 以上两个问题都是比较复杂的问题,并且需要多个步骤分析才能得出结果,初看时候很少有思路的,这个时候,我们通过举例分析,一步一步来看,当最后一步符合,或者卡在某一个步骤时候,我们此时往往能从当前的状态看出解题的思路。