剑指Offer-23-栈的压入、弹出序列

题目

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)。

解析

预备知识

栈是一种“后进先出”的线性表,我们在线性表的尾部进行添加和删除元素。很常见的生活中的例子,就是洗盘子。我们所用过得盘子,都是最后收拾的盘子在上面,所以第一个要洗的就是第一个盘子。还有火车进站出站等等。再举个程序员都知道的例子,就是我们函数调用栈的原型也是来源栈的数据结构。其中每一次函数调用和结束都对应一次入栈和出栈的过程。每一次函数调用称为调用栈的一个栈帧,它包括操作数栈、本地变量表、动态链接和返回地址。函数A调用函数B,函数B调用函数C,函数C运行完毕,继续执行函数B,最后是函数A。
给定一个栈的压入顺序,可以有多个合法的弹出序列。因为我们不要求必须一次性把全部的数据都压入栈中,入栈和出栈是可以交叉进行,所以会让你判断是否是合法的弹出序列。

思路一

思路一般都是申请一个辅助栈来模拟这个过程即可。我们只要按弹出序列的要求来把控入栈顺序即可。我们以实际的例子来模拟这个过程。假设给定的压入顺序为1,2,3,4。弹出顺序为3,2,4,1。判断是否是合法弹出序列。
1. 首先弹出序列的第一个值为3,说明第一个要弹出的是3,所以我们按照压入顺序依次进栈,直到栈顶等于3,这时我们弹出3。
2. 然后弹出序列的第二值为2,说明下一个要弹出的是2,而这时栈顶恰好是2,所以不必进栈操作,直接弹出栈顶2。
3. 弹出序列的第三个值为4,说明即将要弹出的是4,而此时栈顶为1,所以要继续进栈直到与4相等。按照给定的压栈顺序可知,下一个就是4。所以此时栈顶是4,弹出4即可。
4. 弹出序列的最后一个值为1,说明要弹出的是1,栈顶恰好为1,所以弹出1。
5. 弹出序列遍历完毕,说明是合法弹出序列!

那么什么时候出现不合法序列呢?结合上述过程,我们发现当栈顶与要弹出的值不一致,会按照给定的压入顺序依次进栈直到与弹出相等。这时就会出现一种情况时,压入顺序已遍历(即全部的数已经压入栈中)还是与弹出的值不一致,这就说明这是不合法的弹出序列。
例子给定1,2,3,4的压入顺序,4,2,3,1的弹出序列。这是不合法的,你可以自己演算一下。

    /**
     * 以弹出序列为主
     * @param pushA
     * @param popA
     * @return
     */
    public static boolean IsPopOrder(int[] pushA, int[] popA) {
        if(pushA == null || popA == null) {
            return false;
        }
        int indexPush = 0;
        int indexPop = 0;
        Stack<Integer> stack = new Stack<>();
        boolean isPopOrder = true;
        //循环条件为弹出序列是否遍历完毕!
        while(indexPop < popA.length) {
            while(stack.isEmpty() || (indexPush < pushA.length
                    && stack.peek() != popA[indexPop])) {
                stack.push(pushA[indexPush++]);
            }
            /**
             * 结束循环后,如果栈顶还不等于弹出序列的当前值
             * 这时压入序列都遍历完了还不能与弹出序列的当前值一样
             * 则说明弹出序列不合法
             */
            if(stack.peek() != popA[indexPop]) {
                isPopOrder = false;
                break;
            }
            //栈顶与弹出序列当前值一样,栈弹出,弹出序列后移下一位
            stack.pop();
            indexPop++;
        }
        return isPopOrder;
    }

思路二

通过思路一,你会发现我们是以弹出序列为主的。那能不能以压入序列为主?即我们每压入一个数据,判读它与栈顶的元素是否相等,若相等,则循环的弹出栈顶直到不相等为止。若不相等,继续压入即可。最后我们判断栈是否为空即可。
假设给定的压入顺序为1,2,3,4。弹出顺序为2,1,4,3。判断是否是合法弹出序列。我们外循环控制着压入顺序的遍历,而内循环则进行弹出操作。
1. 压入1,判断是否与弹出序列的2相等,不相等,继续遍历
2. 压入2,判断是否与弹出序列的2相等,相等,则弹出2,判断是否与弹出序列的1相等,相等弹出1,栈空,结束内循环。
3. 压入3,判断是否与弹出序列的4相等,不相等,继续遍历
4. 压入4,判断是否与弹出序列的4相等,相等,弹出4,继续判断是否与弹出序列的3相等,继续弹出3,。栈空,结束内循环
5. 外循环遍历完毕,判断栈是否为空,为空,合法!

    /**
     * 以压入序列为主
     * 思路清晰,代码简单,推荐这个解法
     * @param pushA
     * @param popA
     * @return
     */
    public static boolean IsPopOrder2(int[] pushA, int[] popA) {
        if(pushA == null || popA == null) {
            return false;
        }
        Stack<Integer> stack = new Stack<>();
        int indexPop = 0;
        for(int i = 0; i < pushA.length; i++) {
            stack.push(pushA[i]);
            while(!stack.isEmpty() && stack.peek() == popA[indexPop]) {
                stack.pop();
                indexPop++;
            }
        }
        return stack.isEmpty();
    }    

总结

多写例子,转化角度。

猜你喜欢

转载自blog.csdn.net/dawn_after_dark/article/details/80919257