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