第6章 栈,队列,优先队列
栈和队列虽然是简单的数据结构,但是使用这些简单的数据结构所解决的算法问题不一定简单。在这一章里,我们将来探索,和栈与队列相关的算法问题。
目录
-
6-1 栈的基础应用 Valid Parentheses
-
6-2 栈和递归的紧密关系 Binary Tree Preorder, Inorder and Postorder Traversal
-
6-3 运用栈模拟递归
-
6-4 队列的典型应用 Binary Tree Level Order Traversal
-
6-5 BFS和图的最短路径 Perfect Squares
-
6-6 优先队列
-
6-7 优先队列相关的算法问题 Top K Frequent Elements
6-1 栈的基础应用 Valid Parentheses
题目: LeetCode 20. 有效的括号
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
示例 1:
输入: “()”
输出: true
示例 2:
输入: “()[]{}”
输出: true
示例 3:
输入: “(]”
输出: false
示例 4:
输入: “([)]”
输出: false
import java.util.Stack;
// 20. Valid Parentheses
// https://leetcode.com/problems/valid-parentheses/description/
// 时间复杂度: O(n)
// 空间复杂度: O(n)
public class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<Character>();
for( int i = 0 ; i < s.length() ; i ++ )
if( s.charAt(i) == '(' || s.charAt(i) == '{' || s.charAt(i) == '[')
stack.push(s.charAt(i));
else{
if( stack.size() == 0 )
return false;
Character c = stack.pop();
Character match;
if( s.charAt(i) == ')' )
match = '(';
else if( s.charAt(i) == ']' )
match = '[';
else{
assert s.charAt(i) == '}';
match = '{';
}
if(c != match)
return false;
}
if( stack.size() != 0 )
return false;
return true;
}
private static void printBool(boolean b){
System.out.println(b ? "True" : "False");
}
public static void main(String[] args) {
printBool((new Solution()).isValid("()"));
printBool((new Solution()).isValid("()[]{}"));
printBool((new Solution()).isValid("(]"));
printBool((new Solution()).isValid("([)]"));
}
}
课后作业: LeetCode 150、71
6-2 栈和递归的紧密关系 Binary Tree Preorder, Inorder and Postorder Traversal
递归算法:执行一个函数时又去调用本身(内存中是重新执行这个函数,与去执行另一个函数一样),保存信息栈,然后去执行本身的新函数,以此类推,实质是栈的实现。
二叉树中的递归算法
- 二叉树的先序遍历
- 二叉树的中序遍历
- 二叉树的后序遍历
题目: LeetCode 144. 二叉树的前序遍历
给定一个二叉树,返回它的 前序 遍历。
示例:
输入: [1,null,2,3]
1
2
/
3
输出: [1,2,3]
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
import java.util.ArrayList;
import java.util.List;
/// 144. Binary Tree Preorder Traversal
/// https://leetcode.com/problems/binary-tree-preorder-traversal/description/
/// 二叉树的前序遍历
/// 时间复杂度: O(n), n为树的节点个数
/// 空间复杂度: O(h), h为树的高度
public class Solution144 {
// Definition for a binary tree node.
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
public List<Integer> preorderTraversal(TreeNode root) {
ArrayList<Integer> res = new ArrayList<Integer>();
preorderTraversal(root, res);
return res;
}
private void preorderTraversal(TreeNode node, List<Integer> list){
if(node != null){
list.add(node.val);
preorderTraversal(node.left, list);
preorderTraversal(node.right, list);
}
}
}
题目: LeetCode 94. 二叉树的中序遍历
给定一个二叉树,返回它的中序 遍历。
示例:
输入: [1,null,2,3]
1
2
/
3
输出: [1,3,2]
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
import java.util.ArrayList;
import java.util.List;
/// 94. Binary Tree Inorder Traversal
/// https://leetcode.com/problems/binary-tree-inorder-traversal/solution/
/// 二叉树的中序遍历
/// 时间复杂度: O(n), n为树的节点个数
/// 空间复杂度: O(h), h为树的高度
public class Solution094 {
// Definition for a binary tree node.
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
public List<Integer> inorderTraversal(TreeNode root) {
ArrayList<Integer> res = new ArrayList<Integer>();
inorderTraversal(root, res);
return res;
}
private void inorderTraversal(TreeNode node, List<Integer> list){
if(node != null){
inorderTraversal(node.left, list);
list.add(node.val);
inorderTraversal(node.right, list);
}
}
}
题目: LeetCode 145. 二叉树的后序遍历
给定一个二叉树,返回它的 后序 遍历。
示例:
输入: [1,null,2,3]
1
2
/
3
输出: [3,2,1]
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
import java.util.ArrayList;
import java.util.List;
/// 145. Binary Tree Postorder Traversal
/// https://leetcode.com/problems/binary-tree-postorder-traversal/description/
/// 二叉树的后序遍历
/// 时间复杂度: O(n), n为树的节点个数
/// 空间复杂度: O(h), h为树的高度
public class Solution145 {
// Definition for a binary tree node.
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
public List<Integer> postorderTraversal(TreeNode root) {
ArrayList<Integer> res = new ArrayList<Integer>();
postorderTraversal(root, res);
return res;
}
private void postorderTraversal(TreeNode node, List<Integer> list){
if(node != null){
postorderTraversal(node.left, list);
postorderTraversal(node.right, list);
list.add(node.val);
}
}
}
6-3 运用栈模拟递归
运用栈模拟递归过程,实现二叉树的非递归遍历
题目: LeetCode 144. 二叉树的前序遍历
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
/// 144. Binary Tree Preorder Traversal
/// https://leetcode.com/problems/binary-tree-preorder-traversal/description/
/// 非递归二叉树的前序遍历
/// 时间复杂度: O(n), n为树的节点个数
/// 空间复杂度: O(h), h为树的高度
public class Solution144 {
// Definition for a binary tree node.
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
private class Command{
String s; // go, print
TreeNode node;
Command(String s, TreeNode node){
this.s = s;
this.node = node;
}
};
public List<Integer> preorderTraversal(TreeNode root) {
ArrayList<Integer> res = new ArrayList<Integer>();
if(root == null)
return res;
Stack<Command> stack = new Stack<Command>();
stack.push(new Command("go", root));
while(!stack.empty()){
Command command = stack.pop();
if(command.s.equals("print"))
res.add(command.node.val);
else{
assert command.s.equals("go");
if(command.node.right != null)
stack.push(new Command("go",command.node.right));
if(command.node.left != null)
stack.push(new Command("go",command.node.left));
stack.push(new Command("print", command.node));
}
}
return res;
}
}
题目: LeetCode 94. 二叉树的中序遍历
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
/// 94. Binary Tree Inorder Traversal
/// https://leetcode.com/problems/binary-tree-inorder-traversal/solution/
/// 非递归二叉树的中序遍历
/// 时间复杂度: O(n), n为树的节点个数
/// 空间复杂度: O(h), h为树的高度
public class Solution094 {
// Definition for a binary tree node.
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
private class Command{
String s; // go, print
TreeNode node;
Command(String s, TreeNode node){
this.s = s;
this.node = node;
}
};
public List<Integer> inorderTraversal(TreeNode root) {
ArrayList<Integer> res = new ArrayList<Integer>();
if(root == null)
return res;
Stack<Command> stack = new Stack<Command>();
stack.push(new Command("go", root));
while(!stack.empty()){
Command command = stack.pop();
if(command.s.equals("print"))
res.add(command.node.val);
else{
assert command.s.equals("go");
if(command.node.right != null)
stack.push(new Command("go",command.node.right));
stack.push(new Command("print", command.node));
if(command.node.left != null)
stack.push(new Command("go",command.node.left));
}
}
return res;
}
}
题目: LeetCode 145. 二叉树的后序遍历
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
/// 145. Binary Tree Postorder Traversal
/// https://leetcode.com/problems/binary-tree-postorder-traversal/description/
/// 非递归的二叉树的后序遍历
/// 时间复杂度: O(n), n为树的节点个数
/// 空间复杂度: O(h), h为树的高度
public class Solution145 {
// Definition for a binary tree node.
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
private class Command{
String s; // go, print
TreeNode node;
Command(String s, TreeNode node){
this.s = s;
this.node = node;
}
};
public List<Integer> postorderTraversal(TreeNode root) {
ArrayList<Integer> res = new ArrayList<Integer>();
if(root == null)
return res;
Stack<Command> stack = new Stack<Command>();
stack.push(new Command("go", root));
while(!stack.empty()){
Command command = stack.pop();
if(command.s.equals("print"))
res.add(command.node.val);
else{
assert command.s.equals("go");
stack.push(new Command("print", command.node));
if(command.node.right != null)
stack.push(new Command("go",command.node.right));
if(command.node.left != null)
stack.push(new Command("go",command.node.left));
}
}
return res;
}
}
课后作业: LeetCode 341
6-4 队列的典型应用 Binary Tree Level Order Traversal
队列的基本应用:广度优先遍历
- 树:层次遍历
- 图:无权图的最短路径
题目: LeetCode 102. 二叉树的层次遍历
给定一个二叉树,返回其按层次遍历的节点值。 (即逐层地,从左到右访问所有节点)。
例如:
给定二叉树: [3,9,20,null,null,15,7],
3
/
9 20
/
15 7
返回其层次遍历结果:
[
[3],
[9,20],
[15,7]
]
import java.util.ArrayList;
import java.util.List;
import java.util.LinkedList;
import javafx.util.Pair;
/// 102. Binary Tree Level Order Traversal
/// https://leetcode.com/problems/binary-tree-level-order-traversal/description/
/// 二叉树的层序遍历
/// 时间复杂度: O(n), n为树的节点个数
/// 空间复杂度: O(n)
class Solution {
// Definition for a binary tree node.
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
public List<List<Integer>> levelOrder(TreeNode root) {
ArrayList<List<Integer>> res = new ArrayList<List<Integer>>();
if(root == null)
return res;
// 我们使用LinkedList来做为我们的先入先出的队列
LinkedList<Pair<TreeNode, Integer>> queue = new LinkedList<Pair<TreeNode, Integer>>();
queue.addLast(new Pair<TreeNode, Integer>(root, 0));
while(!queue.isEmpty()){
Pair<TreeNode, Integer> front = queue.removeFirst();
TreeNode node = front.getKey();
int level = front.getValue();
if(level == res.size())
res.add(new ArrayList<Integer>());
assert level < res.size();
res.get(level).add(node.val);
if(node.left != null)
queue.addLast(new Pair<TreeNode, Integer>(node.left, level + 1));
if(node.right != null)
queue.addLast(new Pair<TreeNode, Integer>(node.right, level + 1));
}
return res;
}
}
课后作业: LeetCode 107、103、199
6-5 BFS和图的最短路径 Perfect Squares
题目: LeetCode 279. 完全平方数
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
示例 1:
输入: n = 12
输出: 3
解释: 12 = 4 + 4 + 4.
示例 2:
输入: n = 13
输出: 2
解释: 13 = 4 + 9.
import java.util.LinkedList;
import javafx.util.Pair;
// 279. Perfect Squares
// https://leetcode.com/problems/perfect-squares/description/
// 该方法会导致 Time Limit Exceeded 或者 Memory Limit Exceeded
//
// 时间复杂度: O(2^n)
// 空间复杂度: O(2^n)
public class Solution1 {
public int numSquares(int n) {
LinkedList<Pair<Integer, Integer>> queue = new LinkedList<Pair<Integer, Integer>>();
queue.addLast(new Pair<Integer, Integer>(n, 0));
while(!queue.isEmpty()){
Pair<Integer, Integer> front = queue.removeFirst();
int num = front.getKey();
int step = front.getValue();
if(num == 0)
return step;
for(int i = 1 ; num - i*i >= 0 ; i ++)
queue.addLast(new Pair(num - i * i, step + 1));
}
throw new IllegalStateException("No Solution.");
}
public static void main(String[] args) {
System.out.println((new Solution1()).numSquares(12));
System.out.println((new Solution1()).numSquares(13));
}
}
import java.util.LinkedList;
import javafx.util.Pair;
// 279. Perfect Squares
// https://leetcode.com/problems/perfect-squares/description/
// 使用visited数组,记录每一个入队元素
//
// 时间复杂度: O(n)
// 空间复杂度: O(n)
public class Solution2 {
public int numSquares(int n) {
LinkedList<Pair<Integer, Integer>> queue = new LinkedList<Pair<Integer, Integer>>();
queue.addLast(new Pair<Integer, Integer>(n, 0));
boolean[] visited = new boolean[n+1];
visited[n] = true;
while(!queue.isEmpty()){
Pair<Integer, Integer> front = queue.removeFirst();
int num = front.getKey();
int step = front.getValue();
if(num == 0)
return step;
for(int i = 1 ; num - i*i >= 0 ; i ++)
if(!visited[num - i * i]){
queue.addLast(new Pair(num - i * i, step + 1));
visited[num - i * i] = true;
}
}
throw new IllegalStateException("No Solution.");
}
public static void main(String[] args) {
System.out.println((new Solution2()).numSquares(12));
System.out.println((new Solution2()).numSquares(13));
}
}
import java.util.LinkedList;
import javafx.util.Pair;
// 279. Perfect Squares
// https://leetcode.com/problems/perfect-squares/description/
// 进一步优化
//
// 时间复杂度: O(n)
// 空间复杂度: O(n)
public class Solution3 {
public int numSquares(int n) {
if(n == 0)
return 0;
LinkedList<Pair<Integer, Integer>> queue = new LinkedList<Pair<Integer, Integer>>();
queue.addLast(new Pair<Integer, Integer>(n, 0));
boolean[] visited = new boolean[n+1];
visited[n] = true;
while(!queue.isEmpty()){
Pair<Integer, Integer> front = queue.removeFirst();
int num = front.getKey();
int step = front.getValue();
if(num == 0)
return step;
for(int i = 1 ; num - i*i >= 0 ; i ++){
int a = num - i*i;
if(!visited[a]){
if(a == 0) return step + 1;
queue.addLast(new Pair(num - i * i, step + 1));
visited[num - i * i] = true;
}
}
}
throw new IllegalStateException("No Solution.");
}
public static void main(String[] args) {
System.out.println((new Solution3()).numSquares(12));
System.out.println((new Solution3()).numSquares(13));
}
}
课后作业: LeetCode 127、126
6-6 优先队列
题目: Java语言优先队列使用细节
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Random;
public class Main {
public static void main(String[] args) {
// 默认的PriorityQueue, 底层是最小堆
PriorityQueue<Integer> pq = new PriorityQueue<Integer>();
for(int i = 0 ; i < 10 ; i ++){
int num = (int)(Math.random() * 100);
pq.add(num);
System.out.println("insert " + num + " in priority queue.");
}
while (!pq.isEmpty())
System.out.print(pq.poll() + " ");
System.out.println();
System.out.println();
// 使用lambda表达式,创建底层是最大堆的PriorityQueue
PriorityQueue<Integer> pq2 = new PriorityQueue<Integer>(10, (a, b) -> b - a);
for(int i = 0 ; i < 10 ; i ++){
int num = (int)(Math.random() * 100);
pq2.add(num);
System.out.println("insert " + num + " in priority queue.");
}
while (!pq2.isEmpty())
System.out.print(pq2.poll() + " ");
System.out.println();
System.out.println();
// 使用自定义的Comparator,创建个性化的PriorityQueue
// 注意:也可以使用lambda表达式。在这里只是为了演示PriorityQueue的不同用法
// 同理,上一个例子也可以使用自定义的Comparator的方式完成
class myCmp implements Comparator<Integer>{
@Override
public int compare(Integer a, Integer b){
if(a%10 != b%10)
return a%10 - b%10;
return a - b;
}
}
PriorityQueue<Integer> pq3 = new PriorityQueue<Integer>(10, new myCmp());
for(int i = 0 ; i < 10 ; i ++){
int num = (int)(Math.random() * 100);
pq3.add(num);
System.out.println("insert " + num + " in priority queue.");
}
while (!pq3.isEmpty())
System.out.print(pq3.poll() + " ");
System.out.println();
System.out.println();
}
}
6-7 优先队列相关的算法问题 Top K Frequent Elements
题目: LeetCode 347. 前K个高频元素
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
说明:
你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。
import java.util.*;
import java.util.HashMap;
import javafx.util.Pair;
// 347. Top K Frequent Elements
// https://leetcode.com/problems/top-k-frequent-elements/description/
// 时间复杂度: O(nlogk)
// 空间复杂度: O(n + k)
class Solution {
private class PairComparator implements Comparator<Pair<Integer, Integer>>{
@Override
public int compare(Pair<Integer, Integer> p1, Pair<Integer, Integer> p2){
if(p1.getKey() != p2.getKey())
return p1.getKey() - p2.getKey();
return p1.getValue() - p2.getValue();
}
}
public List<Integer> topKFrequent(int[] nums, int k) {
if(k <= 0)
throw new IllegalArgumentException("k should be greater than 0");
// 统计每个元素出现的频率
HashMap<Integer, Integer> freq = new HashMap<Integer, Integer>();
for(int i = 0 ; i < nums.length ; i ++)
if(freq.containsKey(nums[i]))
freq.put(nums[i], freq.get(nums[i]) + 1);
else
freq.put(nums[i], 1);
if(k > freq.size())
throw new IllegalArgumentException("k should be less than the number of unique numbers in nums");
// 扫描freq,维护当前出现频率最高的k个元素
// 在优先队列中,按照频率排序,所以数据对是 (频率,元素) 的形式
PriorityQueue<Pair<Integer, Integer>> pq = new PriorityQueue<Pair<Integer, Integer>>(new PairComparator());
for(Integer num: freq.keySet()){
int numFreq = freq.get(num);
if(pq.size() == k){
if(numFreq > pq.peek().getKey()){
pq.poll();
pq.add(new Pair(numFreq, num));
}
}
else
pq.add(new Pair(numFreq, num));
}
ArrayList<Integer> res = new ArrayList<Integer>();
while(!pq.isEmpty())
res.add(pq.poll().getValue());
return res;
}
private static void printList(List<Integer> nums){
for(Integer num: nums)
System.out.print(num + " ");
System.out.println();
}
public static void main(String[] args) {
int[] nums = {1, 1, 1, 2, 2, 3};
int k = 2;
printList((new Solution()).topKFrequent(nums, k));
}
}