有关二叉树的题目,最好是能掌握其递归的解法,递归的解法熟练运用后,就能清晰的知道题目的整体逻辑,之后再延伸到迭代的解法。
二叉树的所有路径(回溯算法) 相关题目:
此题引入了一种新的算法:回溯算法 。回溯算法是在递归的基础上做的一些优化,本题中要返回二叉树根节点到叶子结点的所有路径,因此要用到 前序遍历 ,在前序遍历的基础上进行回溯处理,代码如下:
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 public List<String> binaryTreePaths (TreeNode root) { List<String> resList = new ArrayList<>(); if (root != null ) { List<Integer> path = new ArrayList<>(); getNode(root, path, resList); } return resList; } public void getNode (TreeNode node, List<Integer> path, List<String> resList) { path.add(node.val); if (node.left == null && node.right == null ) { StringBuilder sb = new StringBuilder(); for (int i = 0 ; i < path.size(); i++) { sb.append(path.get(i)); if (i != path.size() - 1 ) { sb.append("->" ); } } resList.add(sb.toString()); } if (node.left != null ) { getNode(node.left, path, resList); path.remove(path.size() - 1 ); } if (node.right != null ) { getNode(node.right, path, resList); path.remove(path.size() - 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 public List<String> binaryTreePaths (TreeNode root) { List<String> resList = new ArrayList<>(); if (root != null ) { Stack<Object> stack = new Stack<>(); stack.push(root); stack.push(root.val + "" ); while (!stack.empty()) { String path = stack.pop().toString(); TreeNode node = (TreeNode) stack.pop(); if (node.left == null && node.right == null ) { resList.add(path); } if (node.right != null ) { stack.push(node.right); stack.push(path + "->" + node.right.val); } if (node.left != null ) { stack.push(node.left); stack.push(path + "->" + node.left.val); } } } return resList; }
找树左下角的值 相关题目:
直接使用 层序遍历 处理即可,层序遍历每次都从每层的最左侧开始,因此每一层的第一个节点就是最左侧的节点 。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public int findBottomLeftValue (TreeNode root) { Queue<TreeNode> queue = new LinkedList<>(); queue.add(root); int result = root.val; while (!queue.isEmpty()) { int len = queue.size(); for (int i = 0 ; i < len; i++) { TreeNode node = queue.poll(); if (i == 0 ) { result = node.val; } if (node.left != null ) { queue.add(node.left); } if (node.right != null ) { queue.add(node.right); } } } return result; }
再看下 递归法 ,要找最底层最左侧的节点,首先要找到 深度最大的节点 的值。接着找到左侧的节点,因为本题不需要处理中间节点,因此只要保证 遍历时左侧节点在右侧节点前面 即可,而 前中后序遍历都是左侧节点优先遍历 ,因此三种遍历法都可以。代码如下:
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 int maxDeep = -1 ;int value = 0 ;public int findBottomLeftValue (TreeNode root) { recurse(root, 1 ); return value; } public void recurse (TreeNode root, int deep) { if (root == null ) { return ; } if (root.left == null && root.right == null ) { if (deep > maxDeep) { value = root.val; maxDeep = deep; } } if (root.left != null ) { recurse(root.left, deep + 1 ); } if (root.right != null ) { recurse(root.right, deep + 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 int sum = 0 ;public boolean hasPathSum (TreeNode root, int targetSum) { if (root == null ) { return false ; } sum += root.val; if (root.left == null && root.right == null && sum == targetSum) { return true ; } if (root.left != null ) { boolean result = hasPathSum(root.left, targetSum); if (result) { return result; } sum -= root.left.val; } if (root.right != null ) { boolean result = hasPathSum(root.right, targetSum); if (result) { return result; } sum -= root.right.val; } return false ; }
这里可以再优化一下,无需再定义计数器,直接在 targetNum
上进行操作。这样做的好处还有一个:在处理完左右子节点后,无需再手动回溯了 。因为 传递到方法中的基本类型参数的值发生变化后,不会影响到父作用域的原参数值 。代码如下:
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 public boolean hasPathSum (TreeNode root, int targetSum) { if (root == null ) { return false ; } targetSum -= root.val; if (root.left == null && root.right == null && targetSum == 0 ) { return true ; } if (root.left != null ) { boolean result = hasPathSum(root.left, targetSum); if (result) { return result; } } if (root.right != null ) { boolean result = hasPathSum(root.right, targetSum); if (result) { return result; } } return false ; }
再来看下 迭代法 的处理,与 [257.二叉树的所有路径 ] 代码相似,定义的栈里一个元素不仅要记录该节点指针,还要记录从头结点到该节点的路径数值总和 。如下:
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 public boolean hasPathSum (TreeNode root, int targetSum) { if (root == null ) { return false ; } Stack<Object> stack = new Stack<>(); stack.push(root); stack.push(root.val); while (!stack.isEmpty()) { int sum = (int ) stack.pop(); TreeNode node = (TreeNode) stack.pop(); if (node.left == null && node.right == null && sum == targetSum) { return true ; } if (node.right != null ) { stack.push(node.right); stack.push(sum + node.right.val); } if (node.left != null ) { stack.push(node.left); stack.push(sum + node.left.val); } } return false ; }
路径总和ii 再来看下 [113.路径总和ii ] 这题,这题要求返回所有满足条件的路径。
首先看 迭代法 ,在上一题的基础上稍作修改即可,注意,在获取到满足条件的路径时,添加到结果集中的路径集合需要创建一个新的集合,如果仍然使用之前的集合,那之后做回溯时,会造成数据的污染。代码如下:
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 List<List<Integer>> resList = new ArrayList<>(); List<Integer> tmpList = new ArrayList<>(); public List<List<Integer>> pathSum(TreeNode root, int targetSum) { if (root != null ) { recurse(root, targetSum); } return resList; } public void recurse (TreeNode node, int targetNum) { if (node == null ) { return ; } tmpList.add(node.val); targetNum -= node.val; if (node.left == null && node.right == null && targetNum == 0 ) { resList.add(new ArrayList<>(tmpList)); return ; } if (node.left != null ) { recurse(node.left, targetNum); tmpList.remove(tmpList.size() - 1 ); } if (node.right != null ) { recurse(node.right, targetNum); tmpList.remove(tmpList.size() - 1 ); } }
再来看下 迭代法 ,Stack
中要额外存放一个路径集合数据,并且每次迭代都要创建一个新的集合来处理,这样可以避免集合数据被污染,同时能够避免再手动进行回溯处理。代码如下:
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 public List<List<Integer>> pathSum(TreeNode root, int targetSum) { List<List<Integer>> resList = new ArrayList<>(); if (root != null ) { Stack<Object> stack = new Stack<>(); stack.push(root); stack.push(root.val); stack.push(new ArrayList<Integer>()); while (!stack.isEmpty()) { ArrayList<Integer> list = (ArrayList<Integer>) stack.pop(); int sum = (int ) stack.pop(); TreeNode node = (TreeNode) stack.pop(); list.add(node.val); if (node.left == null && node.right == null && sum == targetSum) { resList.add(list); } if (node.right != null ) { stack.push(node.right); stack.push(sum + node.right.val); stack.push(new ArrayList<>(list)); } if (node.left != null ) { stack.push(node.left); stack.push(sum + node.left.val); stack.push(new ArrayList<>(list)); } } } return resList; }
从中序与后续遍历序列构造二叉树 相关题目:
106.从中序与后续遍历序列构造二叉树
105.从前序与中序遍历序列构造二叉树
654.最大二叉树
本题的关键是要对数组进行切割,找到切割点,然后根据切割点将数组分成左右数组,代码如下:
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 public TreeNode buildTree (int [] inorder, int [] postorder) { if (postorder.length == 0 ) { return null ; } TreeNode root = new TreeNode(postorder[postorder.length - 1 ]); if (postorder.length == 1 ) { return root; } int cuttingPoint = 0 ; while (cuttingPoint < inorder.length) { if (inorder[cuttingPoint] == root.val) { break ; } cuttingPoint++; } int [] leftInorder = Arrays.copyOfRange(inorder, 0 , cuttingPoint); int [] rightInorder = Arrays.copyOfRange(inorder, cuttingPoint + 1 , inorder.length); int [] leftPostorder = Arrays.copyOfRange(postorder, 0 , leftInorder.length); int [] rightPostorder = Arrays.copyOfRange(postorder, leftInorder.length, postorder.length - 1 ); root.left = buildTree(leftInorder, leftPostorder); root.right = buildTree(rightInorder, rightPostorder); 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 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 public TreeNode buildTree (int [] inorder, int [] postorder) { if (inorder.length == 0 || postorder.length == 0 ) { return null ; } return recurse(inorder, 0 , inorder.length, postorder, 0 , postorder.length); } public TreeNode recurse (int [] inorder, int inorderBegin, int inorderEnd, int [] postorder, int postorderBegin, int postorderEnd) { if (postorderBegin == postorderEnd) { return null ; } TreeNode root = new TreeNode(postorder[postorderEnd - 1 ]); if (postorder.length == 1 ) { return root; } int cuttingPoint = 0 ; while (cuttingPoint < inorderEnd) { if (inorder[cuttingPoint] == root.val) { break ; } cuttingPoint++; } int leftInorderBegin = inorderBegin; int leftInorderEnd = cuttingPoint; int rightInorderBegin = cuttingPoint + 1 ; int rightInorderEnd = inorderEnd; int leftPostorderBegin = postorderBegin; int leftPostorderEnd = postorderBegin + (leftInorderEnd - leftInorderBegin); int rightPostorderBegin = leftPostorderEnd; int rightPostorderEnd = postorderEnd - 1 ; root.left = recurse(inorder, leftInorderBegin, leftInorderEnd, postorder, leftPostorderBegin, leftPostorderEnd); root.right = recurse(inorder, rightInorderBegin, rightInorderEnd, postorder, rightPostorderBegin, rightPostorderEnd); return root; }
从前序与中序遍历序列构造二叉树 再来看下 [105.从前序与中序遍历序列构造二叉树 ] 这题,与上面的处理类似:
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 public TreeNode buildTree (int [] preorder, int [] inorder) { if (inorder.length == 0 || preorder.length == 0 ) { return null ; } TreeNode root = new TreeNode(preorder[0 ]); int cuttingPoint = 0 ; while (cuttingPoint < inorder.length) { if (inorder[cuttingPoint] == root.val) { break ; } cuttingPoint++; } int [] leftInorder = Arrays.copyOfRange(inorder, 0 , cuttingPoint); int [] rightInorder = Arrays.copyOfRange(inorder, cuttingPoint + 1 , inorder.length); int [] leftPreorder = Arrays.copyOfRange(preorder, 1 , leftInorder.length + 1 ); int [] rightPreorder = Arrays.copyOfRange(preorder, leftInorder.length + 1 , preorder.length); root.left = buildTree(leftPreorder, leftInorder); root.right = buildTree(rightPreorder, rightInorder); 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 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public TreeNode buildTree (int [] preorder, int [] inorder) { if (inorder.length == 0 || preorder.length == 0 ) { return null ; } return recurse(preorder, 0 , preorder.length, inorder, 0 , inorder.length); } public TreeNode recurse (int [] preorder, int preBegin, int preEnd, int [] inorder, int inBegin, int inEnd) { if (preBegin >= preEnd || inBegin >= inEnd) { return null ; } TreeNode root = new TreeNode(preorder[preBegin]); if (preEnd - preBegin == 1 ) { return root; } int cuttingPoint = 0 ; while (cuttingPoint < inorder.length) { if (inorder[cuttingPoint] == root.val) { break ; } cuttingPoint++; } int leftInbegin = inBegin; int leftInend = cuttingPoint; int rightInbegin = cuttingPoint + 1 ; int rightInend = inEnd; int leftPrebegin = preBegin + 1 ; int leftPreend = leftPrebegin + (leftInend - leftInbegin); int rightPrebegin = leftPreend; int rightPreend = preEnd; root.left = recurse(preorder, leftPrebegin, leftPreend, inorder, leftInbegin, leftInend); root.right = recurse(preorder, rightPrebegin, rightPreend, inorder, rightInbegin, rightInend); return root; }
引申一下:想要通过两个遍历序列构造一个二叉树,那这个遍历序列中必须要有中序遍历序列。因为通过前后序遍历可以确定根节点的值,而只有中序遍历才能确定根节点左右子树。 所以,想要通过 前序遍历序列和后序遍历序列 是无法构造二叉树的。
最大二叉树 再来看下 [654.最大二叉树 ] 这题,同样是构造二叉树,与上面两题的处理方式类似:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public TreeNode constructMaximumBinaryTree (int [] nums) { if (nums.length == 0 ) { return null ; } if (nums.length == 1 ) { return new TreeNode(nums[0 ]); } int maxIndex = 0 ; for (int i = 0 ; i < nums.length; i++) { if (nums[i] > nums[maxIndex]) { maxIndex = i; } } TreeNode root = new TreeNode(nums[maxIndex]); int [] left = Arrays.copyOfRange(nums, 0 , maxIndex); int [] right = Arrays.copyOfRange(nums, maxIndex + 1 , nums.length); root.left = constructMaximumBinaryTree(left); root.right = constructMaximumBinaryTree(right); 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 24 public TreeNode constructMaximumBinaryTree (int [] nums) { if (nums.length == 0 ) { return null ; } return recurse(nums, 0 , nums.length); } public TreeNode recurse (int [] nums, int begin, int end) { if (begin >= end) { return null ; } int maxIndex = begin; for (int i = begin; i < end; i++) { if (nums[i] > nums[maxIndex]) { maxIndex = i; } } TreeNode root = new TreeNode(nums[maxIndex]); root.left = recurse(nums, begin, maxIndex); root.right = recurse(nums, maxIndex + 1 , end); 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 24 25 26 27 28 29 30 31 32 33 public TreeNode mergeTrees (TreeNode root1, TreeNode root2) { if (root1 == null ) { return root2; } if (root2 == null ) { return root1; } Queue<TreeNode> queue = new LinkedList<>(); queue.add(root1); queue.add(root2); while (!queue.isEmpty()) { TreeNode node1 = queue.poll(); TreeNode node2 = queue.poll(); node1.val += node2.val; if (node1.left != null && node2.left != null ) { queue.add(node1.left); queue.add(node2.left); } if (node1.right != null && node2.right != null ) { queue.add(node1.right); queue.add(node2.right); } if (node1.left == null && node2.left != null ) { node1.left = node2.left; } if (node1.right == null && node2.right != null ) { node1.right = node2.right; } } return root1; }