算法总结之栈和队列


栈和队列常见算法题目

实现可查询最值的特殊栈

定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的min函数。
思路
(1)定义两个栈,一个正常栈 stackDate ,一个栈 stackMin 用来存储每个元素入栈后,此时栈内的最小元素
(2)如果入栈元素小于 stackMin 栈顶元素则入栈,大于则栈顶元素再次入栈;出栈时同时出栈

		public class Solution {
		    private Stack<Integer> stackDate= new Stack<Integer>();
		    private Stack<Integer> stackMin= new Stack<Integer>();
		    
		    public void push(int node) {
		        stackDate.push(node);
		        if(stackMin.empty()||node<=stackMin.peek()){
		            stackMin.push(node);
		        }else{
		            stackMin.push(stackMin.peek());
		        }
		    }
		    
		    public void pop() {
		        stackDate.pop();
		        stackMin.pop();
		    }
		    
		    public int top() {
		        return stackDate.peek();
		    }
		    
		    public int min() {
		        return stackMin.peek();
		    }
		}

双栈实现队列

编写一个类,只能用两个栈结构实现队列,支持队列的基本操作(push,pop)。
给定一个操作序列ope及它的长度n,其中元素为正数代表push操作,为0代表pop操作,保证操作序列合法且一定含pop操作,请返回pop的结果序列。
测试样例:
[1,2,3,0,4,0],6
返回:[1,2]
思路:两个栈,一个专门Push,一个专门Pop

		public class TwoStack {
		    public int[] twoStack(int[] ope, int n) {
		        Stack<Integer> stackPush= new Stack<Integer>();
		        Stack<Integer> stackPop= new Stack<Integer>();
		        int num = 0;
		        
		        for(int i=0;i<n;i++){
		            if(ope[i]==0){
		                num++;
		            }
		            if(ope[i]>0){
		                stackPush.push(ope[i]);
		            }
		        }
		        int[] res = new int[num];
		        while(!stackPush.empty()){
		            stackPop.push(stackPush.pop());
		        }
		        for(int i=0;i<num;i++){
		            res[i] = stackPop.pop();
		        }
		        return res;
		    }
		}

栈的反转

实现一个栈的逆序,但是只能用递归函数和这个栈本身的pop操作来实现,而不能自己申请另外的数据结构。
给定一个整数数组A即为给定的栈,同时给定它的大小n,请返回逆序后的栈。
测试样例:
[4,3,2,1],4
返回:[1,2,3,4]
思路:递归实现一个移除并返回栈底元素的方法,通过该方法递归实现栈的逆序方法

		public class StackReverse {
		    public int[] reverseStack(int[] A, int n) {
		        Stack<Integer> stack= new Stack<Integer>();//为了切合题意,定义一个栈
		        for(int i=0;i<n;i++){
		            stack.push(A[i]);
		        }
		        reverse(stack);
		        for(int i=n-1;i>=0;i--){
		            A[i] = stack.pop();
		        }
		        return A;
		    }
		    //将栈逆序
		    private void reverse(Stack<Integer> stack){
		        if(stack.empty())
		            return;
		        int temp = get(stack);
		        reverse(stack);
		        stack.push(temp);
		    }
		    //递归移除栈底元素并返回
		    private int get(Stack<Integer> stack){
		        int res = stack.pop();
		        if(stack.empty())
		            return res;
		        else{
		            int temp = get(stack);
		            stack.push(res);
		            return temp;
		        }
		    }
		}

递归函数
1.递归的思想:某个问题可以分为多个子问题,而为了解决这个问题,必须先解决其依赖的子问题,例如:
递归处理的顺序就是,先开始处理的问题,最后才能结束处理
假设如下问题的依赖关系:【A】----依赖---->【B】----依赖---->【C】
三个问题的处理顺序如下:
开始处理问题A;
由于A依赖B,因此开始处理问题B;
由于B依赖C,开始处理问题C;
结束处理问题C;
结束处理问题B;
结束处理问题A。

2.递归与栈的关系
由处理顺序可以看出,其实这是一个入栈与出栈的过程
递归函数是利用系统中栈进行操作的,通过对栈帧的一系列操作,从而实现递归
补:https://blog.csdn.net/qq_36503007/article/details/82897043


双栈排序

请编写一个程序,按升序对栈进行排序(即最大元素位于栈顶),要求最多只能使用一个额外的栈存放临时数据,但不得将元素复制到别的数据结构中。
给定一个int[] numbers,其中第一个元素为栈顶,请返回排序后的栈。请注意这是一个栈,意味着排序过程中你只能访问到第一个元素。
测试样例:
[1,2,3,4,5]
返回:[5,4,3,2,1]
思路:建立一个辅助栈,栈为空或 元素 比栈顶元素大则直接入栈,否则辅助栈中大于该元素的都出栈并入数据栈,将该元素入栈后,出栈的那些元素再依次回辅助栈;当数据栈为空时,将辅助栈的元素倒回,则数据栈用的元素就是升序的

		public class TwoStacks {
		    public ArrayList<Integer> twoStacksSort(int[] numbers) {
		        Stack<Integer> stack = new Stack<Integer>();
		        Stack<Integer> help = new Stack<Integer>();
		        ArrayList<Integer> res = new ArrayList<>();
		        for(int i=0;i<numbers.length;i++){
		            stack.push(numbers[i]);
		        }
		        while(!stack.empty()){
		            int temp = stack.pop();
		            if(help.empty())
		                help.push(temp);
		            else{
		                if(temp>=help.peek())
		                    help.push(temp);
		                else{
		                    while(!help.empty() && help.peek()>temp){
		                    /*注意
		                    &&运算符第一个表达式不成立的话,后面的表达式不运算,直接返回;所以此处顺序不能反
		                    */
		                        stack.push(help.pop());
		                    }
		                    help.push(temp);
		                }
		            }
		        }
		        while(!help.empty()){
		            res.add(help.pop());
		        }
		        return res;
		    }
		}

滑动窗口

有一个整型数组 arr 和一个大小为 w 的窗口从数组的最左边滑到最右边,窗口每次向右边滑一个位置。 返回一个长度为n-w+1的数组res,res[i]表示每一种窗口状态下的最大值。 以数组为[4,3,5,4,3,3,6,7],w=3为例。因为第一个窗口[4,3,5]的最大值为5,第二个窗口[3,5,4]的最大值为5,第三个窗口[5,4,3]的最大值为5。第四个窗口[4,3,3]的最大值为4。第五个窗口[3,3,6]的最大值为6。第六个窗口[3,6,7]的最大值为7。所以最终返回[5,5,5,4,6,7]。
给定整形数组arr及它的大小n,同时给定w,请返回res数组。保证w小于等于n,同时保证数组大小小于等于500。
测试样例:
[4,3,5,4,3,3,6,7],8,3
返回:[5,5,5,4,6,7]
思路
(1)定义一个双端队列(允许两端都可以进行入队和出队操作的队列),用以保存元素的下标
(2)遍历数组,元素与队列中下标的对应元素比较(从后往前),小则从后入队;大则队尾元素依次弹出,直至队尾元素比此元素大,该元素入队
(3)根据下标位置和窗口长度,弹出队首,即队首值等于 i-w时
(4)当遍历长度等于窗口长度时,开始记录队首对应的元素即为每个窗口内的最大值

		public class SlideWindow {
		    public int[] slide(int[] arr, int n, int w) {
		        if(arr==null||n==0||n<w)
		            return null;
		        int[] res = new int[arr.length - w + 1];
		        LinkedList<Integer> qmax = new LinkedList<Integer>();
		        int index = 0;
		        for(int i=0;i<n;i++){
		            while(!qmax.isEmpty() && arr[i]>=arr[qmax.getLast()]){
		                qmax.removeLast();
		            }
		            qmax.add(i);
		            if(qmax.getFirst() == i-w)
		                qmax.removeFirst();
		            if(i>=w-1)
		                res[index++] = arr[qmax.getFirst()];
		        }
		        return res;
		    }
}

数组变树

对于一个没有重复元素的整数数组,请用其中元素构造一棵MaxTree,MaxTree定义为一棵二叉树,其中的节点与数组元素一一对应,同时对于MaxTree的每棵子树,它的根的元素值为子树的最大值。现有一建树方法,对于数组中的每个元素,其在树中的父亲为数组中它左边比它大的第一个数和右边比它大的第一个数中更小的一个。若两边都不存在比它大的数,那么它就是树根。请设计O(n)的算法实现这个方法。
给定一个无重复元素的数组A和它的大小n,请返回一个数组,其中每个元素为原数组中对应位置元素在树中的父亲节点的编号,若为根则值为-1。
测试样例:
[3,1,4,2],4
返回:[2,0,-1,2]
思路
(1)遍历数组,保存左侧第一个大于此元素的下标,没有则保存为 -1
(2)遍历数组,保存右侧第一个大于此元素的下标,没有则保存为-1
(3)两侧都为 -1 则为根节点,父节点为元素左右两侧下标对应元素小的节点

		public class MaxTree {
		    public int[] buildMaxTree(int[] A, int n) {
		        Stack<Integer> temp = new Stack<Integer>();//保留坐标信息,便于返回
		        int[] res = new int[n];
		        //保留左侧大于每个值的下标
		        for(int i=0;i<n;i++){
		            while(!temp.empty() && A[temp.peek()]<A[i]){
		                temp.pop();
		            }
		            if(temp.empty())
		                res[i] = -1;
		            else
		                res[i] = temp.peek();//先保留左侧大的值的下标,再将下一个入栈(区别于思路)
		            temp.push(i);
		        }
		        temp.clear();
		        //左右右侧大于每个值的下标的那个小的下标
		        for(int i=n-1;i>=0;i--){
		            while(!temp.empty() && A[temp.peek()]<A[i]){
		                temp.pop();
		            }
		            if(!temp.empty() && (res[i]==-1||A[temp.peek()]<A[res[i]]))//只有两侧res[i]==-1时才是根,如果右侧的小则改变res[i],否则不变
		                res[i] = temp.peek();
		            temp.push(i);
		        }
		        return res;
		    }
		}

猜你喜欢

转载自blog.csdn.net/MOKEXFDGH/article/details/88096260