1. 树
1.1 【简单】二叉树的前序遍历(144)
/**
* 树的前序遍历
*/
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
while (root != null || !stack.isEmpty()) {
while (root != null) {
list.add(root.value);
stack.push(root);
root = root.left;
}
if (!stack.isEmpty()) {
root = stack.pop();
root = root.right;
}
}
return list;
}
1.2 【简单】二叉树的中序遍历(94)
/**
* 树的中序遍历
*/
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
while (root != null || !stack.isEmpty()) {
while (root != null) {
stack.push(root);
root = root.left;
}
if (!stack.isEmpty()) {
root = stack.pop();
result.add(root.value);
root = root.right;
}
}
return result;
}
1.3 【简单】二叉树的后序遍历(145)
/**
* 树的后序遍历
*/
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> stack1 = new Stack<>();
Stack<Integer> stack2 = new Stack<>();
int left = 1;
int right = 2;
while (root != null || !stack1.isEmpty()) {
while (root != null) {
stack1.push(root);
stack2.push(left);
root = root.left;
}
while (!stack1.isEmpty() && stack2.peek() == right) {
stack2.pop();
result.add(stack1.pop().value);
}
if (!stack1.isEmpty() && stack2.peek() == left) {
root = stack1.peek().right;
stack2.pop();
stack2.push(right);
}
}
return result;
}
1.4 【简单】N 叉树的前序遍历(589)
/**
* N叉树的前序遍历
*/
public List<Integer> preorder(Node root) {
List<Integer> res = new ArrayList<>();
if (root == null) {
return res;
}
Deque<Node> stack = new ArrayDeque<>();
stack.push(root);
while (!stack.isEmpty()) {
Node node = stack.pop();
res.add(node.val);
for (int i = node.children.size() - 1; i >= 0; --i) {
stack.push(node.children.get(i));
}
}
return res;
}
1.5 【简单】N 叉树的后序遍历(590)
/**
* N叉树的后序遍历,可以利用中右左反转即为左右中(后序)
*/
public List<Integer> postorder(Node root) {
List<Integer> res = new ArrayList<>();
if (root == null) {
return res;
}
Deque<Node> stack = new ArrayDeque<>();
stack.push(root);
while (!stack.isEmpty()) {
Node node = stack.pop();
res.add(node.val);
for (Node item : node.children) {
stack.push(item);
}
}
Collections.reverse(res);
return res;
}
1.6 【中等】N 叉树的层序遍历(429)
/**
* 队列即可,与二叉树的层序遍历相似
*/
public List<List<Integer>> levelOrder(Node root) {
List<List<Integer>> ret = new ArrayList<>();
if (root == null) {
return ret;
}
Queue<Node> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
List<Integer> level = new ArrayList<>();
int currentLevelSize = queue.size();
for (int i = 1; i <= currentLevelSize; ++i) {
Node node = queue.poll();
level.add(node.val);
if (node.children != null) {
for (Node child : node.children) {
queue.offer(child);
}
}
}
ret.add(level);
}
return ret;
}
1.7 【中等】从前序与中序遍历序列构造二叉树(105)
递归
/**
* 递归构建左右节点,先统计出各自的数量以及确定index
*/
public class BuildTree {
private Map<Integer, Integer> indexMap;
public TreeNode myBuildTree(int[] preorder, int[] inorder, int preorderLeft, int preorderRight, int inorderLeft, int inorderRight) {
if (preorderLeft > preorderRight) {
return null;
}
// 前序遍历中的第一个节点就是根节点
int preorderRoot = preorderLeft;
// 在中序遍历中定位根节点
int inorderRoot = indexMap.get(preorder[preorderRoot]);
// 先把根节点建立出来
TreeNode root = new TreeNode(preorder[preorderRoot]);
// 得到左子树中的节点数目
int sizeLeftSubtree = inorderRoot - inorderLeft;
// 递归地构造左子树,并连接到根节点
// 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
root.left = myBuildTree(preorder, inorder, preorderLeft + 1, preorderLeft + sizeLeftSubtree, inorderLeft, inorderRoot - 1);
// 递归地构造右子树,并连接到根节点
// 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
root.right = myBuildTree(preorder, inorder, preorderLeft + sizeLeftSubtree + 1, preorderRight, inorderRoot + 1, inorderRight);
return root;
}
public TreeNode buildTree(int[] preorder, int[] inorder) {
int n = preorder.length;
// 构造哈希映射,帮助我们快速定位根节点
indexMap = new HashMap<>();
for (int i = 0; i < n; i++) {
indexMap.put(inorder[i], i);
}
return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 1);
}
}
非递归
/**
* 有点难,记下关键步骤
*/
public TreeNode myBuildTree(int[] preorder, int[] inorder) {
if (preorder == null || preorder.length == 0) {
return null;
}
// 前序遍历最左边元素即为根节点
TreeNode root = new TreeNode(preorder[0]);
Deque<TreeNode> stack = new LinkedList<>();
stack.push(root);
int inorderIndex = 0;
for (int i = 1; i < preorder.length; i++) {
int preorderVal = preorder[i];
TreeNode node = stack.peek();
// 如果相等了就是当前节点下的最左节点
if (node.value != inorder[inorderIndex]) {
node.left = new TreeNode(preorderVal);
stack.push(node.left);
} else {
// 如果不等就是右节点,否则一直出栈
while (!stack.isEmpty() && stack.peek().value == inorder[inorderIndex]) {
node = stack.pop();
inorderIndex++;
}
node.right = new TreeNode(preorderVal);
stack.push(node.right);
}
}
return root;
}
1.8 【中等】根据前序和后序遍历构造二叉树(889)
/**
* 我们令左分支有 L 个节点。我们知道左分支的头节点为 pre[1],但它也出现在左分支的后序遍历的最后。
*/
public TreeNode constructFromPrePost(int[] preorder, int[] postorder) {
int N = preorder.length;
if (N == 0) {
return null;
}
TreeNode root = new TreeNode(preorder[0]);
if (N == 1) {
return root;
}
int L = 0;
for (int i = 0; i < N; ++i) {
if (postorder[i] == preorder[1]) {
L = i + 1;
}
}
root.left = constructFromPrePost(Arrays.copyOfRange(preorder, 1, L + 1),
Arrays.copyOfRange(postorder, 0, L));
root.right = constructFromPrePost(Arrays.copyOfRange(preorder, L + 1, N),
Arrays.copyOfRange(postorder, L, N - 1));
return root;
}
1.9 【中等】从中序与后序遍历序列构造二叉树(106)
递归
/**
* 后序遍历末尾元素即为根节点,在根据中序遍历进行构建左右子树
*/
public class BuildTree {
int postIdx;
int[] postorder;
int[] inorder;
Map<Integer, Integer> idxMap = new HashMap<>();
public TreeNode helper(int inLeft, int inRight) {
// 如果这里没有节点构造二叉树了,就结束
if (inLeft > inRight) {
return null;
}
// 选择 postIdx 位置的元素作为当前子树根节点
int rootVal = postorder[postIdx];
TreeNode root = new TreeNode(rootVal);
// 根据 root 所在位置分成左右两棵子树
int index = idxMap.get(rootVal);
// 下标减一
postIdx--;
// 构造右子树
root.right = helper(index + 1, inRight);
// 构造左子树
root.left = helper(inLeft, index - 1);
return root;
}
public TreeNode buildTree(int[] inorder, int[] postorder) {
this.postorder = postorder;
this.inorder = inorder;
// 从后序遍历的最后一个元素开始
postIdx = postorder.length - 1;
// 建立(元素,下标)键值对的哈希表
int idx = 0;
for (Integer val : inorder) {
idxMap.put(val, idx++);
}
return helper(0, inorder.length - 1);
}
}
非递归
/**
* 中序遍历反一下即为右中左,后序遍历反一下为中右左,所以用跟1.7的方法即可
*/
public TreeNode buildTree2(int[] inorder, int[] postorder) {
if (postorder == null || postorder.length == 0) {
return null;
}
TreeNode root = new TreeNode(postorder[postorder.length - 1]);
Deque<TreeNode> stack = new LinkedList<>();
stack.push(root);
int inorderIndex = inorder.length - 1;
for (int i = postorder.length - 2; i >= 0; i--) {
int postorderVal = postorder[i];
TreeNode node = stack.peek();
if (node.value != inorder[inorderIndex]) {
node.right = new TreeNode(postorderVal);
stack.push(node.right);
} else {
while (!stack.isEmpty() && stack.peek().value == inorder[inorderIndex]) {
node = stack.pop();
inorderIndex--;
}
node.left = new TreeNode(postorderVal);
stack.push(node.left);
}
}
return root;
}
1.10 【中等】路径总和 II(113)
深度优先搜索
/**
* 深度优先搜索,dfs递归
*/
public class PathSum {
List<List<Integer>> ret = new LinkedList<>();
Deque<Integer> path = new LinkedList<>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
dfs(root, targetSum);
return ret;
}
public void dfs(TreeNode root, int targetSum) {
if (root == null) {
return;
}
path.offerLast(root.value);
targetSum -= root.value;
if (root.left == null && root.right == null && targetSum == 0) {
ret.add(new LinkedList<>(path));
}
dfs(root.left, targetSum);
dfs(root.right, targetSum);
path.pollLast();
}
}
广度优先搜索
/**
* 广度优先搜索
*/
public class PathSum {
List<List<Integer>> ret = new LinkedList<>();
// key:子节点,value:父节点
Map<TreeNode, TreeNode> map = new HashMap<>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
if (root == null) {
return ret;
}
Queue<TreeNode> queueNode = new LinkedList<>();
Queue<Integer> queueSum = new LinkedList<>();
queueNode.offer(root);
queueSum.offer(0);
while (!queueNode.isEmpty()) {
TreeNode node = queueNode.poll();
int rec = queueSum.poll() + node.value;
if (node.left == null && node.right == null) {
if (rec == targetSum) {
// 符合路径,构造路径返回
getPath(node);
}
} else {
if (node.left != null) {
// 左节点添加
map.put(node.left, node);
queueNode.offer(node.left);
queueSum.offer(rec);
}
if (node.right != null) {
// 右节点添加
map.put(node.right, node);
queueNode.offer(node.right);
queueSum.offer(rec);
}
}
}
return ret;
}
public void getPath(TreeNode node) {
List<Integer> temp = new LinkedList<>();
while (node != null) {
temp.add(node.value);
node = map.get(node);
}
// 倒序节点
Collections.reverse(temp);
ret.add(new LinkedList<>(temp));
}
}
1.11 【困难】剑指 Offer II 048. 序列化与反序列化二叉树
/**
* 递归遍历即可
*/
public String serialize(TreeNode root) {
return rserialize(root,"");
}
public TreeNode deserialize(String data) {
String[] dataArray = data.split(",");
List<String> dataList = new LinkedList<>(Arrays.asList(dataArray));
return rdeserialize(dataList);
}
public String rserialize(TreeNode root, String str) {
if (root == null) {
str += "None,";
} else {
str += root.value + ",";
str = rserialize(root.left, str);
str = rserialize(root.right, str);
}
return str;
}
public TreeNode rdeserialize(List<String> dataList) {
if ("None".equals(dataList.get(0))) {
dataList.remove(0);
return null;
}
TreeNode root = new TreeNode(Integer.valueOf(dataList.get(0)));
dataList.remove(0);
root.left = rdeserialize(dataList);
root.right = rdeserialize(dataList);
return root;
}
1.12 【中等】二叉树的最近公共祖先
class Solution {
private TreeNode ans;
public Solution() {
this.ans = null;
}
private boolean dfs(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) return false;
boolean lson = dfs(root.left, p, q);
boolean rson = dfs(root.right, p, q);
if ((lson && rson) || ((root.val == p.val || root.val == q.val) && (lson || rson))) {
ans = root;
}
return lson || rson || (root.val == p.val || root.val == q.val);
}
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
this.dfs(root, p, q);
return this.ans;
}
}
定义 fx 表示 x 节点的子树中是否包含 p 节点或 q 节点,如果包含为 true,否则为 false
2. 递归
2.1 【简单】爬楼梯(70)
递归
public int climbStairs(int n) {
if (n <= 2) {
return n;
}
return climbStairs( n - 1 ) + climbStairs( n - 2 );
}
非递归
public int climbStairs(int n) {
int p = 0, q = 0, r = 1;
for (int i = 1; i <= n; ++i) {
p = q;
q = r;
r = p + q;
}
return r;
}
2.2 【中等】括号生成(22)
递归
/**
* 递归
*/
public class GenerateParenthesis {
List<String> res = new ArrayList<>();
public List<String> generateParenthesis(int n) {
if (n <= 0) {
return res;
}
getParenthesis("", n, n);
return res;
}
private void getParenthesis(String str, int left, int right) {
if (left == 0 && right == 0) {
res.add(str);
return;
}
if (left == right) {
//剩余左右括号数相等,下一个只能用左括号
getParenthesis(str + "(", left - 1, right);
} else if (left < right) {
//剩余左括号小于右括号,下一个可以用左括号也可以用右括号
if (left > 0) {
getParenthesis(str + "(", left - 1, right);
}
getParenthesis(str + ")", left, right - 1);
}
}
}
回溯
/**
* 回溯
*/
public class GenerateParenthesis2 {
public List<String> generateParenthesis(int n) {
List<String> ans = new ArrayList<>();
backtrack(ans, new StringBuilder(), 0, 0, n);
return ans;
}
public void backtrack(List<String> ans, StringBuilder cur, int open, int close, int max) {
if (cur.length() == max * 2) {
ans.add(cur.toString());
return;
}
if (open < max) {
cur.append('(');
backtrack(ans, cur, open + 1, close, max);
// 删除最后一个元素,方便寻找其他可能性
cur.deleteCharAt(cur.length() - 1);
}
if (close < open) {
cur.append(')');
backtrack(ans, cur, open, close + 1, max);
cur.deleteCharAt(cur.length() - 1);
}
}
}
2.3 【简单】汉诺塔问题(面试题 08.06.)
/**
* 将 A 上的所有盘子,借助 B,移动到C 上
*
* @param A 原柱子
* @param B 辅助柱子
* @param C 目标柱子
*/
public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {
movePlate(A.size(), A, B, C);
}
private void movePlate(int num, List<Integer> original, List<Integer> auxiliary, List<Integer> target) {
if (num == 1) {
// 只剩一个盘子时,直接移动即可
target.add(original.remove(original.size() - 1));
return;
}
// 将 size-1 个盘子,从 original 移动到 auxiliary
movePlate(num - 1, original, target, auxiliary);
// 将 第size个盘子,从 original 移动到 target
target.add(original.remove(original.size() - 1));
// 将 size-1 个盘子,从 auxiliary 移动到 target
movePlate(num - 1, auxiliary, original, target);
}