遍历
递归版本(前/中/后)
public static void orderRecursive(TreeNode root) {
if (root == null) return ;
//只要改变访问当前节点的顺序就行
//1.先序
System.out.println(root.val);
preOrderRecursive(root.left);
//2.中序 System.out.println(root.val);
preOrderRecursive(root.right);
//3.后序 System.out.println(root.val);
}
1.先序遍历
非递归
public void preOrderNoRecursive(TreeNode root) {
if (root == null) return ;
Stack<TreeNode> stack = new Stack<>();
TreeNode curNode = root;
while (!stack.isEmpty() || curNode != null) {
while (curNode != null) {
//访问节点
stack.push(curNode);
curNode = curNode.left;
}
curNode = stack.pop();
curNode = curNode.right;
}
}
2.中序遍历
二叉搜索树的中序遍历刚好是从小到大的顺序,这是比较特殊的一点。
非递归
和先序的类似
public void preOrder(TreeNode root) {
if (root == null) return ;
Stack<TreeNode> stack = new Stack<>();
TreeNode curNode = root;
while (!stack.isEmpty() || curNode != null) {
while (curNode != null) {
stack.push(curNode);
curNode = curNode.left;
}
//访问节点
curNode = stack.pop();
curNode = curNode.right;
}
}
反序的中序遍历只需要将上面left和right两行代码互换即可,在一些题目中可能会出现,比如538。
Morris中序
空间复杂度为O(1),核心是找前驱节点,在遍历的过程中树的结构会发生改变,但在遍历后会恢复成遍历前的结构。
需要两个指针,当前指针curNode
和前驱指针preNode
:
- 如果左子节点为空,遍历当前节点,并使得当前节点
curNode
指向右子节点,开始新的遍历就行。 - 如果左子节点不为空,则当前节点的前驱节点一定位于左子树中,开始寻找左子树中最右侧节点,可能会出现两种情况:
- 如果遍历到的最右侧叶节点的右子节点为空,表明该叶节点是
curNode
的前驱节点,因此把该叶节点的右指针指向当前节点(这样是为了后面遍历到叶节点后还能回到curNode
),然后另curNode
指向左子节点,开始新的遍历。 - 如果遍历到的最右侧节点的右子节点是
curNode
,表示该左子树已经全部遍历完毕,需要复原,因此将右子节点重新置为空,并遍历curNode。
使得当前节点curNode
指向右子节点,开始新的遍历就行。public void morrisInOrder(TreeNode root) {
TreeNode curNode = root, preNode = null; //注意当前节点curNode的更新只有两处
while (curNode != null) {
//如果当前节点没有左子树,则遍历然后跳转右子树
if (curNode.left == null) {
//遍历curNode
curNode = curNode.right;
continue;
}
preNode = curNode.left;
//前驱节点一定在左子树上
while (preNode.right != null && preNode.right != curNode) {
preNode = preNode.right;
}
if (preNode.right == null) { //第一次找到前驱节点
preNode.right = curNode;
curNode = curNode.left; //从curNode.left重新开始遍历
} else {
preNode.right = null; //复原
//遍历curNode
curNode = curNode.right;
}
}
}
3. 后序遍历
容易理解的方法:(根,左,右) -> (根,右,左) -> (左,右,根)public List<Integer> postorderTraversal(TreeNode root) {
Stack<TreeNode> stack = new Stack();
List<Integer> postOrderList = new ArrayList();
TreeNode curNode = root;
while (!stack.isEmpty() || curNode != null) {
while (curNode != null) {
postOrderList.add(curNode.val);
stack.push(curNode);
curNode = curNode.right;
}
curNode = stack.pop();
curNode = curNode.left;
}
Collections.reverse(postOrderList);
return postOrderList;
}
public List<Integer> postorderTraversal(TreeNode root) {
Stack<TreeNode> stack = new Stack();
List<Integer> postOrderList = new ArrayList();
TreeNode curNode = root, prev = null;
while (!stack.isEmpty() || curNode != null) {
while (curNode != null) {
stack.push(curNode);
curNode = curNode.left;
}
curNode = stack.pop();
if (curNode.right == null || curNode.right == prev) {
postOrderList.add(curNode.val);
prev = curNode;
curNode = null;
} else {
stack.push(curNode);
curNode = curNode.right;
}
}
return postOrderList;
}
4. 层次遍历
116. 填充每个节点的下一个右侧节点指针链接
重点是空间如何优化为O(1),需要想到上层节点是一个链表。
dummy节点能够避免头结点为空的特判情况,代码好看一点。public Node connect(Node root) {
Node curLevelNode = root;
while (curLevelNode != null) {
Node nextLevelDummyNode = new Node(-1), nextLevelCurNode = nextLevelDummyNode;
while (curLevelNode != null) {
Node left = curLevelNode.left;
Node right = curLevelNode.right;
if (left == null) break;
nextLevelCurNode.next = left;
left.next = right;
nextLevelCurNode = right;
curLevelNode = curLevelNode.next;
}
curLevelNode = nextLevelDummyNode.next;
nextLevelDummyNode.next = null;
}
return root;
}
117. 填充每个节点的下一个右侧节点指针 II链接
相比于上一题,没有了完全二叉树的限制,算是比较通用的,稍微麻烦了一点。//常规的层次遍历,时间/空间复杂度都是O(n)
public Node connect(Node root) {
if (root == null) return root;
LinkedList<Node> queue = new LinkedList();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
while (size-- > 0) {
Node curNode = queue.poll();
curNode.next = (size > 0 ? queue.get(0) : null);
if (curNode.left != null) queue.offer(curNode.left);
if (curNode.right != null) queue.offer(curNode.right);
}
}
return root;
}
public Node connect(Node root) {
Node curLevelNode = root;
while (curLevelNode != null) {
Node nextLevelDummyNode = new Node(-1), nextLevelCurNode = nextLevelDummyNode;
while (curLevelNode != null) {
Node left = curLevelNode.left;
Node right = curLevelNode.right;
if (left != null) {
nextLevelCurNode.next = left;
left.next = right;
nextLevelCurNode = left;
}
if (right != null) {
nextLevelCurNode.next = right;
nextLevelCurNode = right;
}
curLevelNode = curLevelNode.next;
}
curLevelNode = nextLevelDummyNode.next;
nextLevelDummyNode.next = null;
}
return root;
}
LeetCode
- 如果遍历到的最右侧叶节点的右子节点为空,表明该叶节点是
106. 从中序和后序遍历序列中构造二叉树
关键是找出中序节点中根节点所在的位置,然后递归构造,需要注意各种边界条件。
public TreeNode buildTree(int[] inorder, int[] postorder) {
if (inorder == null || postorder == null) return null;
return buildTree(inorder, 0, inorder.length-1, postorder, 0, postorder.length-1);
}
public TreeNode buildTree(int[] inorder, int il, int ir, int[] postorder, int pl, int pr) {
if (il < 0 || ir >= inorder.length || pl < 0 || pr >= postorder.length) return null;
if (ir < il || pr < pl || ir-il != pr-pl) return null;
int rootIdx = findRootIndex(inorder, il, ir, postorder[pr]);
if (rootIdx == -1) return null;
TreeNode root = new TreeNode(postorder[pr]);
root.left = buildTree(inorder, il, rootIdx-1, postorder, pl, pl+rootIdx-il-1);
root.right = buildTree(inorder, rootIdx+1, ir, postorder, pl+rootIdx-il, pr-1);
return root;
}
public int findRootIndex(int[] inorder, int l, int r, int rootVal) {
int i = l;
for (; i <= r; i++) {
if (inorder[i] == rootVal) break;
}
return i <= r ? i : -1;
}
113. 路径总和 II
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int sum) {
pathSum(root, sum, new ArrayList<Integer>());
return res;
}
public void pathSum (TreeNode root, int sum, List<Integer> curPath) {
if (root == null) return ;
sum -= root.val;
curPath.add(root.val);
if (root.left == null && root.right == null && sum == 0) {
res.add(new ArrayList<Integer>(curPath));
return ;
}
pathSum(root.left, sum, curPath);
pathSum(root.right, sum, curPath);
curPath.remove(curPath.size()-1);
}
235. 二叉搜索树的最近公共祖先
- 所有节点的值都是唯一的。
- p、q 为不同节点且均存在于给定的二叉搜索树中。
如果没有给出上述的限制的话,感觉边界还挺麻烦的?
public TreeNode lowestCommonAncestorRecursive(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || p == null || q == null) return null;
if (root.val > p.val && root.val > q.val) return lowestCommonAncestor(root.left, p, q);
if (root.val < p.val && root.val < q.val) return lowestCommonAncestor(root.right, p, q);
return root;
}
//二次遍历
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || p == null || q == null) return null;
List<TreeNode> path1 = getPath(root, p), path2 = getPath(root, q);
TreeNode ancestor = null;
for (int i = 0; i < path1.size() && i < path2.size(); i++) {
if (path1.get(i) == path2.get(i)) ancestor = path1.get(i);
else break;
}
return ancestor;
}
public List<TreeNode> getPath(TreeNode root, TreeNode target) {
if (root == null || target == null) return null;
List<TreeNode> path = new ArrayList();
TreeNode curNode = root;
while ((curNode != null) && curNode.val != target.val) {
path.add(curNode);
if (curNode.val < target.val) curNode = curNode.right;
else curNode = curNode.left;
}
path.add(curNode);
return path;
}
//一次遍历
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || p == null || q == null) return null;
TreeNode ancestor = root;
while (true) {
if (p.val < ancestor.val && q.val < ancestor.val) ancestor = ancestor.left;
else if (p.val > ancestor.val && q.val > ancestor.val) ancestor = ancestor.right;
else break;
}
return ancestor;
}