遍历
递归版本(前/中/后)
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;
}
 
                         
                                

