LeetCode 面试经典 150 题-二叉树 深度优先搜索 当前节点最大深度为max(左子树最大深度, 右子树最大深度) + 1.
1 2 3 4 5 6 7 8 class Solution { public int maxDepth (TreeNode root) { if (root == null ) { return 0 ; } return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1 ; } }
时间复杂度:O(n)
空间复杂度:最坏情况下,二叉树退化成一条链,递归需要 O(n) 的栈空间。
广度优先搜索 每次拓展下一层的时候,不同于广度优先搜索的每次只从队列里拿出一个节点,我们需要将队列里的所有节点都拿出来进行拓展,这样能保证每次拓展完的时候队列里存放的是当前层的所有节点,即我们是一层一层地进行拓展,最后我们用一个变量 ans 来维护拓展的次数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Solution { public int maxDepth (TreeNode root) { if (root == null ) { return 0 ; } Queue<TreeNode> que = new LinkedList <>(); que.offer(root); int ans = 0 ; while (!que.isEmpty()) { int size = que.size(); for (int i = 0 ; i < size; i++) { TreeNode node = que.poll(); if (node.left != null ) { que.offer(node.left); } if (node.right != null ) { que.offer(node.right); } } ans++; } return ans; } }
递归 如果当前结点不相同,则返回false,否则递归判断左子树和右子树是否相同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { public boolean isSameTree (TreeNode p, TreeNode q) { if (p == null && q == null ) { return true ; } if (p == null || q == null ) { return false ; } if (p.val != q.val) { return false ; } return isSameTree(p.left, q.left) && isSameTree(p.right, q.right); } }
深度优先搜索
边界处理,为空直接返回;
递归翻转左右子树;
翻转当前结点;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public TreeNode invertTree (TreeNode root) { if (root == null ) { return root; } TreeNode left = invertTree(root.left); TreeNode right = invertTree(root.right); root.left = right; root.right = left; return root; } }
深度优先搜索 需要判断左右子树值是否相等,以及左子树的左子树是否等于右子树的右子树,以及左子树的右子树是否等于右子树的左子树。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public boolean isSymmetric (TreeNode root) { if (root == null ) { return true ; } return helper(root.left, root.right); } private boolean helper (TreeNode left, TreeNode right) { if (left == null || right == null ) { return left == right; } return left.val == right.val && helper(left.left, right.right) && helper(left.right, right.left); } }
广度优先搜索 使用队列实现,每次弹出两个结点,比较其是否相等,在添加结点时,按照相反的顺序添加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public boolean isSymmetric (TreeNode root) { Queue<TreeNode> que = new LinkedList <>(); que.offer(root); que.offer(root); while (!que.isEmpty()) { TreeNode u = que.poll(); TreeNode v = que.poll(); if (u == null && v == null ) { continue ; } if (u == null || v == null || u.val != v.val) { return false ; } que.offer(u.left); que.offer(v.right); que.offer(u.right); que.offer(v.left); } return true ; } }
分析
将中序遍历序列存入哈希表,方便查找。
定义递归函数处理:
递归结束:前序数组遍历完;
O(1)时间在中序遍历中找到根节点;
递归遍历左右子树;
连接子树,返回当前节点;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { public TreeNode buildTree (int [] preorder, int [] inorder) { int n = preorder.length; Map<Integer, Integer> index = new HashMap <>(); for (int i = 0 ; i < n; i++) { index.put(inorder[i], i); } return dfs(preorder, index, 0 , n, 0 , n); } private TreeNode dfs (int [] preorder, Map<Integer, Integer> index, int preL, int preR, int inL, int inR) { if (preL == preR) { return null ; } int leftSize = index.get(preorder[preL]) - inL; TreeNode left = dfs(preorder, index, preL + 1 , preL+1 +leftSize, inL, inL+leftSize); TreeNode right = dfs(preorder, index, preL+1 +leftSize, preR, inL+1 +leftSize, inR); return new TreeNode (preorder[preL], left, right); } }
分析 同上题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public TreeNode buildTree (int [] inorder, int [] postorder) { int n = inorder.length; Map<Integer, Integer> index = new HashMap <>(); for (int i = 0 ; i < n; i++) { index.put(inorder[i], i); } return dfs(postorder, index, 0 , n, 0 , n); } private TreeNode dfs (int [] postorder, Map<Integer, Integer> index, int postL, int postR, int inL, int inR) { if (postL == postR) { return null ; } int leftSize = index.get(postorder[postR-1 ]) - inL; TreeNode left = dfs(postorder, index, postL, postL + leftSize, inL, inL + leftSize); TreeNode right = dfs(postorder, index, postL + leftSize, postR - 1 , inL + leftSize + 1 , inR); return new TreeNode (postorder[postR-1 ], left, right); } }
层序遍历 使用队列实现层序遍历。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { public Node connect (Node root) { if (root == null ) { return root; } Queue<Node> que = new LinkedList <>(); que.offer(root); while (!que.isEmpty()) { int n = que.size(); Node pre = null ; for (int i = 0 ; i < n; i++) { Node node = que.poll(); if (i != 0 ) { pre.next = node; } pre = node; if (node.left != null ) { que.offer(node.left); } if (node.right != null ) { que.offer(node.right); } } } return root; } }
BFS+链表
从第一层开始(第一层只有一个 root 节点),每次循环:
遍历当前层的链表节点,通过节点的 left 和 right 得到下一层的节点。
把下一层的节点从左到右连接成一个链表。
拿到下一层链表的头节点,进入下一轮循环。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public Node connect (Node root) { Node dummy = new Node (); Node cur = root; while (cur != null ) { dummy.next = null ; Node nxt = dummy; while (cur != null ) { if (cur.left != null ) { nxt.next = cur.left; nxt = cur.left; } if (cur.right != null ) { nxt.next = cur.right; nxt = cur.right; } cur = cur.next; } cur = dummy.next; } return root; } }
先序遍历-递归 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public void flatten (TreeNode root) { List<TreeNode> arr = new ArrayList <>(); preOrder(root, arr); for (int i = 1 ; i < arr.size(); i++) { TreeNode pre = arr.get(i - 1 ); TreeNode cur = arr.get(i); pre.left = null ; pre.right = cur; } } private void preOrder (TreeNode root, List<TreeNode> arr) { if (root != null ) { arr.add(root); preOrder(root.left, arr); preOrder(root.right, arr); } } }
先序遍历-迭代 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public void flatten (TreeNode root) { List<TreeNode> arr = new ArrayList <>(); Deque<TreeNode> stack = new ArrayDeque <>(); TreeNode node = root; while (node != null || !stack.isEmpty()) { while (node != null ) { arr.add(node); stack.push(node); node = node.left; } node = stack.pop(); node = node.right; } for (int i = 1 ; i < arr.size(); i++) { TreeNode pre = arr.get(i - 1 ); TreeNode cur = arr.get(i); pre.left = null ; pre.right = cur; } } }
Morris遍历
将左子树插入到右子树的地方
将原来的右子树接到左子树的最右边节点
考虑新的右子树的根节点,一直重复上边的过程,直到新的右子树为 null
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public void flatten (TreeNode root) { while (root != null ) { if (root.left == null ) { root = root.right; } else { TreeNode pre = root.left; while (pre.right != null ) { pre = pre.right; } pre.right = root.right; root.right = root.left; root.left = null ; root = root.right; } } } }
后序遍历-递归 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { private TreeNode pre = null ; public void flatten (TreeNode root) { if (root == null ) { return ; } flatten(root.right); flatten(root.left); root.right = pre; root.left = null ; pre = root; } }
后序遍历-迭代 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { public void flatten (TreeNode root) { Deque<TreeNode> stack = new ArrayDeque <>(); TreeNode cur = root; TreeNode pre = null ; while (cur != null || !stack.isEmpty()) { while (cur != null ) { stack.push(cur); cur = cur.right; } cur = stack.peek(); if (cur.left == null || cur.left == pre) { stack.pop(); cur.right = pre; cur.left = null ; pre = cur; cur = null ; } else { cur = cur.left; } } } }
深度优先搜索
递归结束:当前节点为空,返回false。如果当前节点为叶子结点,则判断当前节点值是否等于target。
递归计算左右子树。
1 2 3 4 5 6 7 8 9 10 11 12 class Solution { public boolean hasPathSum (TreeNode root, int targetSum) { if (root == null ) { return false ; } if (root.left == null && root.right == null ) { return root.val == targetSum; } return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val); } }
广度优先搜索 队列中存储结点和当前路径和的键值对,在广度遍历的过程中判断是否是叶子节点且和满足条件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { public boolean hasPathSum (TreeNode root, int targetSum) { if (root == null ) { return false ; } Queue<Pair<TreeNode, Integer>> queue = new LinkedList <>(); queue.offer(new Pair <>(root, root.val)); while (!queue.isEmpty()) { Pair<TreeNode, Integer> pair = queue.poll(); TreeNode node = pair.getKey(); int currentSum = pair.getValue(); if (node.left == null && node.right == null && currentSum == targetSum) { return true ; } if (node.left != null ) { queue.offer(new Pair <>(node.left, currentSum + node.left.val)); } if (node.right != null ) { queue.offer(new Pair <>(node.right, currentSum + node.right.val)); } } return false ; } }
递归-无返回值
递归结束:节点为空。
递归计算左子树数字、右子树数字,求和。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { int ans = 0 ; public int sumNumbers (TreeNode root) { dfs(root, 0 ); return ans; } private void dfs (TreeNode root, int curNum) { if (root == null ) { return ; } curNum = curNum * 10 + root.val; if (root.left == root.right) { ans += curNum; return ; } dfs(root.left, curNum); dfs(root.right, curNum); } }
递归-有返回值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public int sumNumbers (TreeNode root) { return dfs(root, 0 ); } private int dfs (TreeNode root, int curNum) { if (root == null ) { return 0 ; } curNum = curNum * 10 + root.val; if (root.left == root.right) { return curNum; } return dfs(root.left, curNum) + dfs(root.right, curNum); } }
递归
定义递归函数,返回值是从当前节点到叶子节点的最大和,其值等于当前节点值+max(左子树最大和,右子树最大和)。
同时更新最大路径和,区分其与dfs的返回值,它应该等于左子树最大和+右子树最大和+当前节点值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { int ans = Integer.MIN_VALUE;; public int maxPathSum (TreeNode root) { dfs(root); return ans; } private int dfs (TreeNode root) { if (root == null ) { return 0 ; } int left = Math.max(0 , dfs(root.left)); int right = Math.max(0 , dfs(root.right)); ans = Math.max(ans, left + right + root.val); return root.val + Math.max(left, right); } }
递归 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class BSTIterator { private int idx; private List<Integer> arr; public BSTIterator (TreeNode root) { idx = 0 ; arr = new ArrayList <>(); inorderTraversal(root); } public int next () { return arr.get(idx++); } public boolean hasNext () { return idx < arr.size(); } private void inorderTraversal (TreeNode root) { if (root == null ) { return ; } inorderTraversal(root.left); arr.add(root.val); inorderTraversal(root.right); } }
迭代 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class BSTIterator { private TreeNode cur; private Deque<TreeNode> stack; public BSTIterator (TreeNode root) { cur = root; stack = new ArrayDeque <>(); } public int next () { while (cur != null ) { stack.push(cur); cur = cur.left; } cur = stack.pop(); int res = cur.val; cur = cur.right; return res; } public boolean hasNext () { return cur != null || !stack.isEmpty(); } }
递归
递归结束:如果节点为空,返回0.
递归计算左右子树,加1.
1 2 3 4 5 6 7 8 class Solution { public int countNodes (TreeNode root) { if (root == null ) { return 0 ; } return countNodes(root.left) + countNodes(root.right) + 1 ; } }
二分查找+位运算 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 class Solution { public int countNodes (TreeNode root) { if (root == null ) { return 0 ; } int level = 0 ; TreeNode node = root; while (node.left != null ) { level++; node = node.left; } int low = 1 << level; int high = (1 << (level + 1 )) - 1 ; while (low < high) { int mid = (high - low + 1 ) / 2 + low; if (exists(root, level, mid)) { low = mid; } else { high = mid - 1 ; } } return low; } public boolean exists (TreeNode root, int level, int k) { int bits = 1 << (level - 1 ); TreeNode node = root; while (node != null && bits > 0 ) { if ((bits & k) == 0 ) { node = node.left; } else { node = node.right; } bits >>= 1 ; } return node != null ; } }
时间复杂度:$O(h^2)$
空间复杂度:O(1)
递归
遍历树,如果当前节点为空,或者等于p或q,则最近公共祖先就是该节点本身;
否则递归遍历左右子树,如果得到的左右公共祖先都不为空,说明p、q分列左右子树,那么当前节点就是最近公共祖先;
否则p、q同位于左子树或者同位于右子树。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public TreeNode lowestCommonAncestor (TreeNode root, TreeNode p, TreeNode q) { if (root == null || root == p || root == q) { return root; } TreeNode left = lowestCommonAncestor(root.left, p, q); TreeNode right = lowestCommonAncestor(root.right, p, q); if (left != null && right != null ) { return root; } return left == null ? right : left; } }
递归 找每一层最右边的节点:
用一个变量记录当前遍历到的深度,如果当前结果数组的大小等于深度,说明是首次到达该深度,将当前节点加入结果集。
先递归遍历右子树,再递归遍历左子树。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { List<Integer> ans = new ArrayList <>(); public List<Integer> rightSideView (TreeNode root) { dfs(root, 0 ); return ans; } void dfs (TreeNode root, int depth) { if (root == null ) { return ; } if (ans.size() == depth) { ans.add(root.val); } dfs(root.right, depth + 1 ); dfs(root.left, depth + 1 ); } }
迭代 用队列实现层序遍历,将每一层最后一个结点加入结果集。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { List<Integer> ans = new ArrayList <>(); public List<Integer> rightSideView (TreeNode root) { if (root == null ) { return ans; } Queue<TreeNode> que = new LinkedList <>(); que.offer(root); while (!que.isEmpty()) { int size = que.size(); for (int i = 0 ; i < size; i++) { TreeNode node = que.poll(); if (node.left != null ) { que.add(node.left); } if (node.right != null ) { que.add(node.right); } if (i == size - 1 ) { ans.add(node.val); } } } return ans; } }
迭代 计算每一层的和/节点个数即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public List<Double> averageOfLevels (TreeNode root) { List<Double> ans = new ArrayList <>(); Queue<TreeNode> que = new LinkedList <>(); que.offer(root); while (!que.isEmpty()) { int n = que.size(); long sum = 0 ; for (int i = 0 ; i < n; i++) { TreeNode node = que.poll(); if (node.left != null ) que.offer(node.left); if (node.right != null ) que.offer(node.right); sum += node.val; } ans.add(sum * 1.0 / n); } return ans; } }
注意sum要用long防止溢出。
层序遍历 把奇数层节点逆转即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public List<List<Integer>> zigzagLevelOrder (TreeNode root) { List<List<Integer>> ans = new ArrayList <>(); if (root == null ) { return ans; } Queue<TreeNode> que = new LinkedList <>(); que.offer(root); while (!que.isEmpty()) { int n = que.size(); List<Integer> tmp = new ArrayList <>(); for (int i = 0 ; i < n; i++) { TreeNode node = que.poll(); tmp.add(node.val); if (node.left != null ) { que.offer(node.left); } if (node.right != null ) { que.offer(node.right); } } if (ans.size() % 2 == 1 ) { Collections.reverse(tmp); } ans.add(tmp); } return ans; } }
深度优先搜索 把数字排序后,最小差值一定是相邻两个数的差值。由于二叉搜索树的中序遍历得到的序列是有序的,我们可以计算中序遍历中的相邻两数的差值,取最小值,即为答案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { int ans = Integer.MAX_VALUE; int pre = -1 ; public int getMinimumDifference (TreeNode root) { dfs(root); return ans; } void dfs (TreeNode root) { if (root == null ) { return ; } dfs(root.left); if (pre == -1 ) { pre = root.val; } else { ans = Math.min(ans, root.val - pre); pre = root.val; } dfs(root.right); } }
深度优先搜索
中序遍历:递归遍历二叉树的左子树、访问根节点、然后遍历右子树。
计数与判断:每访问一个节点,count 就增加。当 count == k 时,表示当前节点是我们要找的第 k 小节点。
提前结束:一旦找到了第 k 小的元素,就不再继续递归,直接返回结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { private int count = 0 ; private int result = -1 ; public int kthSmallest (TreeNode root, int k) { dfs(root, k); return result; } private void dfs (TreeNode root, int k) { if (root == null || result != -1 ) { return ; } dfs(root.left, k); count++; if (count == k) { result = root.val; return ; } dfs(root.right, k); } }
做一次错一次,例如虽然左子树节点值小于父节点值,右子树节点值大于父节点值,但没有检查左子树和右子树的节点是否满足这些范围限制。
前序遍历 1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution { public boolean isValidBST (TreeNode root) { return isValid(root, Long.MIN_VALUE, Long.MAX_VALUE); } private boolean isValid (TreeNode root, long left, long right) { if (root == null ) { return true ; } long x = root.val; return left < x && x < right && isValid(root.left, left, x) && isValid(root.right, x, right); } }
中序遍历 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { private long pre = Long.MIN_VALUE; public boolean isValidBST (TreeNode root) { if (root == null ) { return true ; } if (!isValidBST(root.left) || root.val <= pre) { return false ; } pre = root.val; return isValidBST(root.right); } }