栈和队列(queue & stack)

1 栈和队列

栈的特点是后入先出,根据这个特点可以临时保存一些数据,之后用到依次再弹出来,常用于 DFS 深度搜索。
队列的特点是先入先出,一般常用于 BFS 广度搜索,类似一层一层的搜索。

2 栈常见题目

2.1 (lee-155) 最小栈

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

  • push(x) —— 将元素 x 推入栈中。
  • pop() —— 删除栈顶的元素。
  • top() —— 获取栈顶元素。
  • getMin() —— 检索栈中的最小元素。

输入
[“MinStack”,“push”,“push”,“push”,“getMin”,“pop”,“top”,“getMin”]
[[ ],[-2],[0],[-3],[ ],[ ],[ ],[ ]]
输出
[null,null,null,null,-3,null,0,-2]
解释
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.

思路:用两个栈实现,一个最小栈始终保证最小值在顶部

public class MinStack {
	private Deque<Integer> stack; 
	private Deque<Integer> minStack;
	private int min;
	
	public MinStack() { //创建最小栈
		stack = new LinkedList<Integer>();
		minStack = new LinkedList<Integer>();
		min = Integer.MAX_VALUE;
	}
	
	public void push(int x) { //入栈
		stack.offerFirst(x);
		min = Math.min(min, x);
		minStack.offerFirst(min);
	}
	
	public void pop() {   //出栈
		stack.pollFirst();
		minStack.pollFirst();
		min = minStack.isEmpty() ? Integer.MAX_VALUE : minStack.peekFirst();
	}
	
	public int top() {   //返回栈顶
		return stack.peekFirst();
	}
	
	public int getMin() { //返回最小元素
		return minStack.peekFirst();	
	}
	
	public static void main(String[] args) {
		MinStack e = new MinStack();
		e.push(1);
		e.push(2);
		e.push(3);
		e.push(4);
		e.pop();
		int p = e.top();
		System.out.println(p);
		int q = e.getMin();
		System.out.println(q);	
	}
}

2.2 (lee-150) 逆波兰表达式求值

根据 逆波兰表示法,求表达式的值。有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
说明:
整数除法只保留整数部分。
给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

输入:tokens = [“2”,“1”,"+",“3”,"*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9

	/**
	 * 使用栈实现后缀表达式
	 * @param tokens
	 * @return
	 */
	public int evalRPN(String[] tokens) {
		Deque<Integer> stack = new LinkedList<Integer>();
		int n = tokens.length;
		for(int i = 0;i <n;i++) {
			String token = tokens[i];
			if(isNumber(token)) {
				stack.push(Integer.parseInt(token));
			}else {
				int num2 = stack.pop();
				int num1 = stack.pop();
				switch(token) {
					case "+":
						stack.push(num1+num2);
						break;
					case "-":
						stack.push(num1-num2);
						break;
					case "*":
						stack.push(num1*num2);
						break;
					case "/":
						stack.push(num1/num2);
						break;
					default:
				}
			}
		}
		return stack.pop();
	}
	
	private boolean isNumber(String token) {
		return !("+".equals(token) || "-".equals(token) || "*".equals(token) || "/".equals(token));
	}

	/**
	 * 创建数组模拟栈
	 * @param tokens
	 * @return
	 */
	public int evalRPN(String[] tokens) {
		int[] nums = new int[tokens.length/2+1]; //一个有效的逆波兰表达式,其长度n一定是奇数,且操作数的个数一定比运算符的个数多1个
		int index = 0; //数组下标 0 的位置对应栈底
		for (String s : tokens) {
			switch (s) {
			case "+":    //如果遇到运算符,则将两个操作数出栈,然后将一个新操作数入栈,因此栈内元素先减少 2 个再增加 1 个,结果是栈内元素减少 1 个
				nums[index - 2] += nums[--index];
				break;
			case "-":
				nums[index - 2] -= nums[--index];
				break;
			case "*":
				nums[index - 2] *= nums[--index];
				break;
			case "/":
				nums[index - 2] /= nums[--index];
				break;
			default:
				nums[index++]  = Integer.parseInt(s); //如果遇到操作数,则将操作数入栈,因此栈内元素增加 1 个
				break;
			}
		}
		return nums[0];
	}

2.3 (lee-394) 字符串解码

给定一个经过编码的字符串,返回它解码后的字符串。编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。

输入:s = “3[a]2[bc]”
输出:“aaabcbc”
输入:s = “3[a2[c]]”
输出:“accaccacc”

	/**
	 * 思路:数字一个栈,字母一个栈,取出来计算就可以了。
	 * @param s
	 * @return
	 */
	public String decodeString(String s) {
		StringBuilder res = new StringBuilder();
		LinkedList<Integer> numStack = new LinkedList<Integer>();
		LinkedList<StringBuilder> resStack = new LinkedList<StringBuilder>();
		int num =0;
		char[] c = s.toCharArray();
		for(int i = 0;i <c.length;i++) {
			if(c[i] >= '0' && c[i] <='9') {
				num = 10* num + Integer.parseInt(c[i] +"");
			}else if(c[i] == '[') {
				numStack.addLast(num);
				resStack.addLast(res);
				num  = 0;
				res = new StringBuilder();
			}else if(c[i] == ']') {
				StringBuilder temp = resStack.removeLast();
				int curNum = numStack.removeLast();
				for(int j = 0;j <curNum;j++) {
					temp.append(res);
				}
				res = temp;
			}else {
				res.append(c[i]);
			}
		}
		return res.toString();
	}

2.4 (lee-94) 二叉树的中序遍历

给定一个二叉树的根节点 root ,返回它的 中序 遍历。

输入:root = [1,null,2,3]
输出:[1,3,2]

	class TreeNode{
		int val;
		TreeNode left;
		TreeNode right;
		TreeNode(int x){
			val = x;
		}
	}
	/**
	 * 给定一个二叉树,返回它的中序遍历。
	 * 思路:通过stack 保存已经访问的元素,用于原路返回
	 * @param root
	 * @return
	 */
	public List<Integer> inorderTraversal(TreeNode root){
		if(root==null) {
			return new LinkedList<>();
		}
		List<Integer> res = new LinkedList<>();
		Deque<TreeNode> stack = new LinkedList<>();
		TreeNode node = root;
		while(node!= null || !stack.isEmpty()) {
			while(node!= null) {
				stack.addLast(node);
				node = node.left;
			}
			node = stack.removeLast();
			res.add(node.val);
			node = node.right;
		}
		return res;
	}

2.5 (lee-133) 克隆图

给你无向 连通 图中一个节点的引用,请你返回该图的 深拷贝(克隆)。

	class Node {
	    public int val;
	    public List<Node> neighbors;
	    public Node() {
	        val = 0;
	        neighbors = new ArrayList<Node>();
	    }
	    public Node(int _val) {
	        val = _val;
	        neighbors = new ArrayList<Node>();
	    }
	    public Node(int _val, ArrayList<Node> _neighbors) {
	        val = _val;
	        neighbors = _neighbors;
	    }
	}
	
	/**
	 * 给你无向连通图中一个节点的引用,请你返回该图的深拷贝(克隆)
	 * 递归:深度优先遍历
	 */
	
	public Node cloneGraph(Node node) {
		if(node==null) {
			return node;
		}
		HashMap<Node,Node> visited = new HashMap<>();
		if(visited.containsKey(node)) { // 如果visited存在node的克隆结点则返回克隆结点
			return visited.get(node);
		}
		Node cloneNode = new Node(node.val,new ArrayList<>());
		visited.put(node, cloneNode);
		
		for (Node neighbor : node.neighbors) {  // 设置好cloneNode结点的克隆结点
			cloneNode.neighbors.add(cloneGraph(neighbor)); // 递归node邻居结点的克隆结点
		}
		return cloneNode;
	}
	
	/**
	 * 广度优先遍历
	 */
	public Node cloneGraph2(Node node) {
		if(node==null) {
			return node;
		}
		HashMap<Node,Node> visited = new HashMap<>();
		LinkedList<Node> queue = new LinkedList<>(); //将原节点添加到队列
		visited.put(node, new Node(node.val, new ArrayList<>())); //克隆第一个节点并存储到哈希表中
		// 广度优先搜索
		while(!queue.isEmpty()) {
			Node n = queue.remove();  // 取出队列的头节点
			for(Node neighbor: n	.neighbors) { // 遍历该节点的邻居
				if(!visited.containsKey(neighbor)) { // 如果没有被访问过,就克隆并存储在哈希表中
					visited.put(neighbor, new Node(neighbor.val, new ArrayList<>()));
					queue.add(neighbor); // 将邻居节点加入队列中
				}
				visited.get(n).neighbors.add(visited.get(neighbor)); // 更新当前节点的邻居列表
			}
		}
		return visited.get(node);
		
	}

2.6 (lee-84) 柱状图中最大的矩形

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积。

输入: [2,1,5,6,2,3]
输出: 10

2.6.1 枚举宽 O(n*2)
	public int largestRectangleArea(int[] heights) {
		int n = heights.length;
		int ans = 0;
		// 枚举左边界
		for(int l = 0;l < n;l++) {
			int minHeight = Integer.MAX_VALUE;
			// 枚举右边界
			for(int r = l;r < n;r++) {
				minHeight = Math.min(minHeight, heights[r]);// 确定高度
				ans = Math.max(ans, (r-l+1)*minHeight); // 计算面积
			}
		}
		return ans;
	}
2.6.2 枚举高 O(n*2)
	public int largestRectangleArea2(int[] heights) {
		int n = heights.length;
		int ans = 0;
		for(int mid = 0;mid < n;mid++) {
			int height = heights[mid];// 枚举高
			int left = mid;
			int right = mid;
			while(left-1 >= 0 && heights[left-1] >= height) { // 确定左边界
				left--;
			}
			while(right+1  < n && heights[right+1] >= height) { // 确定右边界
				right++;
			}
			ans = Math.max(ans, (right-left+1)*height);// 计算面积
		}
		return ans;
	}
2.6.3 使用单调栈 O(N)
	public int largestRectangleArea3(int[] heights) {
		int n = heights.length;
        int[] left = new int[n];
        int[] right = new int[n];
        
        Stack<Integer> mono_stack = new Stack<Integer>();
        for (int i = 0; i < n; ++i) {
            while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) {
                mono_stack.pop();
            }
            left[i] = (mono_stack.isEmpty() ? -1 : mono_stack.peek());
            mono_stack.push(i);
        }

        mono_stack.clear();
        for (int i = n - 1; i >= 0; --i) {
            while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) {
                mono_stack.pop();
            }
            right[i] = (mono_stack.isEmpty() ? n : mono_stack.peek());
            mono_stack.push(i);
        }
        
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            ans = Math.max(ans, (right[i] - left[i] - 1) * heights[i]);
        }
        return ans;
	}
2.6.4 使用deque 用时更少
	public int largestRectangleArea4(int[] heights) {
        int len = heights.length;
        if (len == 0) {
            return 0;
        }
        if (len == 1) {
            return heights[0];
        }
        int[] newHeights = new int[len + 2]; //左右边界包含
        for (int i = 0; i < len; ++i) {
            newHeights[i + 1] = heights[i];
        }
        int maxArea = 0;
        len += 2;
        heights = newHeights;
        Deque<Integer> stack = new LinkedList<>();
        stack.addFirst(0);
        for (int i = 1; i < len; ++i) {
            while (heights[stack.peekFirst()] > heights[i]) {
                int height = heights[stack.removeFirst()];
                int width = i - stack.peekFirst() - 1;
                maxArea = Math.max(maxArea, height * width);
            }
            stack.addFirst(i);
        }
        return maxArea;
    }

2.7 (lee-85) 最大矩形

给定一个仅包含 0 和 1 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。

输入:
[[“1”,“0”,“1”,“0”,“0”],
[“1”,“0”,“1”,“1”,“1”],
[“1”,“1”,“1”,“1”,“1”],
[“1”,“0”,“0”,“1”,“0”]]
输出: 6

思路:将该问题转化为84中柱状图中最大矩形的面积
第一层柱状图的高度[“1”,“0”,“1”,“0”,“0”],最大面积为1;
第二层柱状图的高度[“2”,“0”,“2”,“1”,“1”],最大面积为3;
第三层柱状图的高度[“3”,“1”,“3”,“2”,“2”],最大面积为6;
第四层柱状图的高度[“4”,“0”,“0”,“3”,“0”],最大面积为4;

	public int maximalRectangle(char[][] matrix) {
		if(matrix.length == 0) {
			return 0;
		}
		int col = matrix.length;
		int row = matrix[0].length;
		int[] high = new int[row];
		int res = 0;
		for(int i = 0;i < col;i++) {
			for(int j = 0;j < row;j++) {
				if(matrix[i][j] == '1') {
					high[j] += 1;
				}else {
					high[j] = 0;
				}
			}
			res = Math.max(res, largestRectangleArea1(high));
		}
		return res;
    }

接下来就是求84柱状图中最大矩形的面积,还有其他方法,这里只写了一种。

	/*
	 * 单调栈 (困难)
	 */
	public int largestRectangleArea1(int[] heights) {
		Deque<Integer> stack = new LinkedList<>();
		stack.offerFirst(-1);
		int maxArea = 0;
		for(int i = 0;i < heights.length;i++) {
			while(stack.peekFirst() != -1 && heights[stack.peekFirst()] >= heights[i]) {
				maxArea = Math.max(maxArea, heights[stack.pollFirst()] * (i - stack.peekFirst() - 1));
			}
			stack.offerFirst(i);
		}
		while(stack.peekFirst() != -1) {
			maxArea = Math.max(maxArea, heights[stack.pollFirst()] * (heights.length - stack.peekFirst() - 1));
		}
		return maxArea;
	}

2.8 (lee-227)基本计算器2

给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。整数除法仅保留整数部分(无括号)。
思路:我们可以用一个栈,保存这些(进行乘除运算后的)整数的值,对于加减号后的数字,将其直接压入栈中;对于乘除号后的数字,可以直接与栈顶元素计算,并替换栈顶元素为计算后的结果。
时间复杂度O(n)
空间复杂度O(n)

输入:s = “3+2*2”
输出:7

	public int calculate(String s) {
        Deque<Integer> stack = new LinkedList<Integer>();//数字栈	
        char preSign = '+';  //符号栈用一个变量preSigh代替,只存储上一个符号
        int num = 0;
        int n = s.length();
        for (int i = 0; i < n; ++i) {
        	char cur = s.charAt(i);
            if (cur >= '0') {
                num = num * 10 - '0'+ cur;// 记录当前数字。先减,防溢出
            }
            if ((cur < '0' && cur != ' ' )|| i == n - 1) {
                switch (preSign) {
                    case '+':
                        stack.push(num);
                        break;
                    case '-':
                        stack.push(-num);
                        break;
                    case '*':
                        stack.push(stack.pop() * num);
                        break;
                    default:
                        stack.push(stack.pop() / num);
                }
                preSign = s.charAt(i); //保存当前符号
                num = 0; //数字清0
            }
        }
        int ans = 0;
        while (!stack.isEmpty()) {
            ans += stack.pop();
        }
        return ans;
    }

3 队列常见题目

3.1 (lee-232) 用栈实现队列

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty)
实现 MyQueue 类:

  • void push(int x) 将元素 x 推到队列的末尾
  • int pop() 从队列的开头移除并返回元素
  • int peek() 返回队列开头的元素
  • boolean empty() 如果队列为空,返回 true ;否则,返回 false

输入
[“MyQueue”, “push”, “push”, “peek”, “pop”, “empty”]
[[], [1], [2], [], [], []]
输出
[null, null, null, 1, 1, false]
解释
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false

public class QueueUseStack {
	public QueueUseStack(){}
	
	private Deque<Integer> stackA = new LinkedList<>();//栈A作为队列的入口,负责插入新元素
	private Deque<Integer> stackB = new LinkedList<>();//栈B作为队列的出口,负责移除老元素
	
	//入队操作
	public void enQueue(int x) {
		stackA.addFirst(x);
	}
	
	//出队操作
	public Integer deQueue() {
		if(stackB.isEmpty()) {
			if(stackA.isEmpty()) {
				return null;
			}
			transfer();
		}
		return stackB.removeFirst();
	}
	
	//栈A元素转移到栈B
	private void transfer() {
		while(!stackA.isEmpty()) {
			stackB.addFirst(stackA.removeFirst());
		}
	}

	public static void main(String[] args) {
		QueueUseStack qus = new QueueUseStack();
		qus.enQueue(1);
		qus.enQueue(2);
		qus.enQueue(3);
		System.out.println(qus.deQueue());
		System.out.println(qus.deQueue());
		qus.enQueue(4);
		System.out.println(qus.deQueue());	
	}
}

3.2 (lee-102) 二叉树的层序遍历

给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)

二叉树:[3,9,20,null,null,15,7],
[ [3],
[9,20],
[15,7] ]

	class TreeNode{
		int val;
		TreeNode left;
		TreeNode right;
		TreeNode(int x){
			val = x;
		}
	}
	/**
	 * 二叉树层次遍历
	 * @param root
	 * @return
	 */
	public List<List<Integer>> levelOrder(TreeNode root){
		if(root==null) {
			return null;
		}
		List<List<Integer>> res = new LinkedList<>();
		Queue<TreeNode> queue  = new LinkedList<>();
		queue.offer(root);
		while(!queue.isEmpty()) {   //注意
			int size = queue.size();
			List<Integer> list = new ArrayList<>();
			for(int i = 0;i <size;i++) {
				TreeNode node = queue.remove();
				list.add(node.val);
				if(node.left != null) {
					queue.offer(node.left);
				}
				if(node.right != null) {
					queue.offer(node.right);
				}
			}
			res.add(list);
		}
		return res;
	}

3.3 (lee-542) 01矩阵

给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。两个相邻元素间的距离为 1。

输入
[[0,0,0],
[0,1,0],
[0,0,0]]
输出
[[0,0,0],
[0,1,0],
[0,0,0]]

动态规划

	public int[][] updateMatrix(int[][] matrix){
		int row = matrix.length;
		int col = matrix[0].length;
		int[][] dist = new int[row][col];
		int MAX_TEMP  = Integer.MAX_VALUE/2;
		 // 如果 (i, j) 的元素为 0,那么距离为 0,否则设置成一个很大的数
		for(int i = 0;i < row;i++) {
			for(int j = 0;j < col;j++) {
				if(matrix[i][j]==0) {
					dist[i][j] = 0;
				}else {
					dist[i][j] = MAX_TEMP;
				}
			}
		}
		// 水平向左移动 和 竖直向上移动
		for(int i =0;i < row;i++) {
			for(int j = 0;j < col;j++) {
				if(i-1 >= 0) {
					dist[i][j] = Math.min(dist[i][j], dist[i-1][j]+1);
				}	
				if(j-1 >= 0) {
					dist[i][j] = Math.max(dist[i][j], dist[i][j-1]+1);
				}
			}
		}
		 // 水平向右移动 和 竖直向下移动
		for(int i = row-1;i >= 0;i--) {
			for(int j = col-1;j >= 0;j--) {
				if(i+1 < row) {
					 dist[i][j] = Math.min(dist[i][j], dist[i + 1][j] + 1);
				}
				if(j+1 < col) {
					 dist[i][j] = Math.min(dist[i][j], dist[i][j+1] + 1);
				}
			}
		}
		return dist;
	}

4 总结

  • 熟悉栈的使用场景
    • 后出先出,保存临时值
    • 利用栈 DFS 深度搜索
  • 熟悉队列的使用场景
    • 利用队列 BFS 广度搜索

5 练习链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值