遍历问题

105-从前序与中序遍历序列构造二叉树

image.png
题意分析:

  • 根据树的遍历重构二叉树。

解题思路:

  • 先根据 preorder 遍历找到 root 节点,找到 root 节点在 inorder 的索引位置 idx,然后递归构造左右子树(难点在于如何根据 idx 分隔左右子树区间)。

注意点:
扩展:
代码:

  1. public TreeNode buildTree(int[] preorder, int[] inorder) {
  2. return build(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1);
  3. }
  4. private TreeNode build(int[] preorder, int preStart, int preEnd, int[] inorder, int inStart, int inEnd) {
  5. // 递归结束条件
  6. if (preStart > preEnd) {
  7. return null;
  8. }
  9. System.out.println(Arrays.toString(preorder) + ": prestart=" + preStart + "; preend=" + preEnd);
  10. System.out.println(Arrays.toString(inorder) + " instart=" + inStart +"; inend=" + inEnd);
  11. System.out.println();
  12. int rootVal = preorder[preStart];
  13. // 记录中序遍历中rootVal 对应的位置
  14. int idx = -1;
  15. // 这里每次递归调用时都会重复遍历inorder 数组,可以通过 HashMap 保持 val 对应的 idx 信息
  16. for (int i = inStart; i <= inEnd; i++) {
  17. if (inorder[i] == rootVal) {
  18. idx = i;
  19. break;
  20. }
  21. }
  22. // 根据先序遍历找到新树的 root 节点
  23. TreeNode root = new TreeNode(rootVal);
  24. // left_len = idx - inStart
  25. root.left = build(preorder, preStart + 1, preStart + (idx - inStart), inorder, inStart, idx - 1);
  26. root.right = build(preorder, preStart + (idx - inStart) + 1, preEnd, inorder, idx + 1, inEnd);
  27. if (root.left != null && root.right != null)
  28. System.out.println("root=" + root.val + "; root.left=" + root.left.val + "; root.right=" + root.right.val);
  29. return root;
  30. }

106-从中序与后序遍历序列构造二叉树

image.png
题意分析:

  • 和 105 题目差不多,条件序列不同而已。

解题思路:

  • 先根据 postorder 遍历找到 root 节点,找到 root 节点在 inorder 的索引位置 idx,然后递归构造左右子树(难点在于如何根据 idx 分隔左右子树区间)。

注意点:
扩展:
代码:

  1. public TreeNode buildTree(int[] inorder, int[] postorder) {
  2. return build(inorder, 0, inorder.length - 1, postorder, 0, postorder.length - 1);
  3. }
  4. private TreeNode build(int[] inorder, int inStart, int inEnd, int[] postorder, int postStart, int postEnd) {
  5. if (postStart > postEnd) {
  6. return null;
  7. }
  8. System.out.println(Arrays.toString(inorder) + ": inStart=" + inStart + "; inEnd=" + inEnd);
  9. System.out.println(Arrays.toString(postorder) + " postStart=" + postStart +"; postEnd=" + postEnd);
  10. System.out.println();
  11. int rootVal = postorder[postEnd];
  12. // 记录中序遍历中rootVal 对应的位置
  13. int idx = -1;
  14. for (int i = inStart; i <= inEnd; i++) {
  15. if (inorder[i] == rootVal) {
  16. idx = i;
  17. break;
  18. }
  19. }
  20. // 根据先序遍历找到新树的 root 节点
  21. TreeNode root = new TreeNode(rootVal);
  22. root.left = build(inorder, inStart, idx - 1, postorder, postStart, postStart + (idx - inStart) - 1);
  23. root.right = build(inorder, idx + 1, inEnd, postorder, postStart + (idx - inStart), postEnd - 1);
  24. System.out.println("root=" + root.val + "; root.left=" + root.left + "; root.right=" + root.right);
  25. if (root.left != null && root.right != null)
  26. System.out.println("root=" + root.val + "; root.left=" + root.left.val + "; root.right=" + root.right.val);
  27. return root;
  28. }

二叉树的先序遍历

代码:

  1. public List<Integer> preorderTraversal(TreeNode root) {
  2. List<Integer> res = new ArrayList<>();
  3. preorder(root, res);
  4. return res;
  5. }
  6. public void preorder(TreeNode root, List res) {
  7. if (root == null) {
  8. return;
  9. }
  10. res.add(root.val);
  11. preorder(root.left, res);
  12. preorder(root.right, res);
  13. }
  14. public List<Integer> preorderTraversal2(TreeNode root) {
  15. List<Integer> res = new ArrayList<>();
  16. Deque<TreeNode> stack = new LinkedList<>();
  17. if (root == null) {
  18. return res;
  19. }
  20. stack.push(root);
  21. while (!stack.isEmpty()) {
  22. TreeNode node = stack.pop();
  23. res.add(node.val);
  24. if (node.right != null) {
  25. stack.push(node.right);
  26. }
  27. if (node.left != null) {
  28. stack.push(node.left);
  29. }
  30. }
  31. return res;
  32. }

94-二叉树的中序遍历

image.png
代码:

  1. /**
  2. * 递归思路:
  3. * 1、递归退出条件
  4. * 2、递归体
  5. */
  6. public List<Integer> inorderTraversal(TreeNode root) {
  7. List<Integer> res = new ArrayList<>();
  8. inorder(root, res);
  9. return res;
  10. }
  11. private void inorder(TreeNode root, List<Integer> res) {
  12. if (root == null) {
  13. return;
  14. }
  15. inorder(root.left, res);
  16. res.add(root.val);
  17. inorder(root.right, res);
  18. }
  19. /**
  20. * 非递归思路:
  21. * 1、手动维护栈,什么时候进栈,什么时候出栈
  22. * @param root
  23. * @return
  24. */
  25. public List<Integer> inorderTraversal2(TreeNode root) {
  26. List<Integer> res = new ArrayList<>();
  27. Deque<TreeNode> stack = new LinkedList<>();
  28. while (root != null || !stack.isEmpty()) {
  29. while (root != null) {
  30. stack.push(root);
  31. root = root.left;
  32. }
  33. TreeNode node = stack.pop();
  34. res.add(node.val);
  35. root = node.right;
  36. }
  37. return res;
  38. }

二叉树的后序遍历

代码:

  1. public List<Integer> postorderTraversal(TreeNode root) {
  2. List<Integer> res = new ArrayList<>();
  3. postorder(root, res);
  4. return res;
  5. }
  6. public void postorder(TreeNode root, List res) {
  7. if (root == null) {
  8. return;
  9. }
  10. postorder(root.left, res);
  11. postorder(root.right, res);
  12. res.add(root.val);
  13. }
  14. public List<Integer> postorderTraversal2(TreeNode root) {
  15. List<Integer> res = new ArrayList<>();
  16. Deque<TreeNode> stack = new LinkedList<>();
  17. TreeNode pre = null; // 用于记录上一次访问的节点
  18. while (root != null || !stack.isEmpty()) {
  19. while (root != null) {
  20. stack.push(root);
  21. root = root.left;
  22. }
  23. TreeNode node = stack.pop();
  24. if (node.right != null) {
  25. stack.push(node);
  26. root = node.right;
  27. } else if (node.right == null || node.right == pre){
  28. res.add(node.val);
  29. pre = node;
  30. root = null;
  31. }
  32. }
  33. return res;
  34. }

102-二叉树的层序遍历

image.png
代码:

  1. public List<List<Integer>> levelOrder(TreeNode root) {
  2. List<List<Integer>> res = new ArrayList<>();
  3. if (root == null) {
  4. return res;
  5. }
  6. Queue<TreeNode> queue = new LinkedList<>();
  7. queue.offer(root);
  8. while (!queue.isEmpty()) {
  9. int size = queue.size();
  10. List<Integer> list = new ArrayList<>();
  11. // 逐层记录元素
  12. while (size > 0) {
  13. TreeNode node = queue.poll();
  14. list.add(node.val);
  15. if (node.left != null) {
  16. queue.offer(node.left);
  17. }
  18. if (node.right != null) {
  19. queue.offer(node.right);
  20. }
  21. size--;
  22. }
  23. // 一层元素遍历完了添加到 res 中
  24. res.add(list);
  25. }
  26. return res;
  27. }

路径问题

1104-二叉树寻路

image.png
题意分析:

  • 打印树根节点到 label 的路径,只不过是树奇偶层级数据进行了反向。

解题思路:

  • 按层级遍历完全二叉树,根据对称关系对树进行处理。

注意点:
扩展:
代码:

  1. /**
  2. * 解题思路
  3. * 特点:完全二叉树
  4. * 1、每一行的元素区间为 [2^(level-1), 2^level -1]
  5. * @param label
  6. * @return
  7. */
  8. public List<Integer> pathInZigZagTree(int label) {
  9. // 根据 label 得到所在行数
  10. int level = 1;
  11. // 每一行的最大元素为 2^(row) - 1
  12. while (label > (Math.pow(2, level) - 1)) {
  13. level++;
  14. }
  15. // 因为偶数行是反着的,所以在寻路前先和对称元素交换,在处理偶数行时再交换过来。
  16. if (level % 2 == 0) {
  17. label = (int)(Math.pow(2, level - 1) + Math.pow(2, level)) - 1 -label;
  18. }
  19. List<Integer> path = new ArrayList<>();
  20. while (level > 0) {
  21. // 奇偶行分别处理,奇数行正常处理,偶数行对称处理
  22. if (level % 2 == 0) {
  23. // 拿到对称的元素。(对称两个元素和是固定的)
  24. int val = (int)(Math.pow(2, level - 1) + Math.pow(2, level)) - 1 -label;
  25. path.add(val);
  26. } else {
  27. path.add(label);
  28. }
  29. // label 往父节点走一次,并且行数减 1
  30. label = label / 2;
  31. level--;
  32. }
  33. Collections.reverse(path);
  34. return path;
  35. }

257-二叉树的所有路径

image.png
题意分析:

  • 打印数的所有路径,显然是要遍历树。

解题思路:

  • dfs 深度遍历,遍历到树节点开始回溯。

注意点:
扩展:

  • 能否使用 bfs 广度遍历实现?

代码:

  1. /**
  2. * dfs深度遍历 + 回溯思想(找完一个往回找)
  3. */
  4. List<List<Integer>> res = new LinkedList<>();
  5. public List<List<Integer>> binaryTreePaths(TreeNode root) {
  6. LinkedList<Integer> path = new LinkedList<>();
  7. dfs(root, path);
  8. return res;
  9. }
  10. public void dfs(TreeNode root, LinkedList path) {
  11. if (root == null) {
  12. return;
  13. }
  14. path.offer(root.val);
  15. if (root.left == null && root.right == null) {
  16. res.add(new LinkedList<>(path));
  17. // return;
  18. }
  19. dfs(root.left, path);
  20. dfs(root.right, path);
  21. path.pollLast();
  22. }

112-路径总和 I

image.png
题意分析:

  • 判断是否存在从根节点到叶子节点存在和为 sum 的路径。

解题思路:

  • dfs 递归遍历,不断减去节点值递归。
  • 层序遍历,记录每一层所有节点的和,直到叶子节点,判断是否存在和为 sum 的叶子节点。

注意点:
扩展:

  • 如何输出所有路径?

代码:

  1. /**
  2. * 先序遍历。
  3. *
  4. * 思考:如果存在路径,如何打印出路径?
  5. *
  6. * @param root
  7. * @param sum
  8. * @return
  9. */
  10. public boolean hasPathSum(TreeNode root, int sum) {
  11. // base case
  12. if (root == null) {
  13. return false;
  14. }
  15. sum -= root.val;
  16. if (root.left == null && root.right == null && sum == 0) {
  17. return true;
  18. }
  19. boolean left = hasPathSum(root.left, sum);
  20. boolean right = hasPathSum(root.right, sum);
  21. return left || right;
  22. }

113-路径总和 II

image.png
题意分析:

  • 输出所有和为 sum 的路径,该路径从根节点到叶子节点。

解题思路:

  • dfs。路径问题建议 dfs 算法,可以结合回溯思想遍历到每个叶子节点。
  • bfs(层序遍历)。记录每一层节点中到达该节点的路径总和,并且还要记录其父亲节点。

注意点:

  • bfs 思路要记录父亲节点,方便在找到节点后输出路径。

扩展:
代码:

  1. // bfs
  2. // 记录所有path路径
  3. List<List<Integer>> res = new ArrayList<>();
  4. // path:找到预期节点后向上父节点寻找(树特点:一个节点只有一个父亲节点)
  5. Map<TreeNode, TreeNode> map = new HashMap<>();
  6. public List<List<Integer>> pathSum2(TreeNode root, int targetSum) {
  7. if (root == null) {
  8. return res;
  9. }
  10. // 这两个队列元素是一一对应的
  11. // 记录层序遍历节点(BFS思想)
  12. Queue<TreeNode> queue = new LinkedList<>();
  13. // 记录当前层级所有节点的和
  14. Queue<Integer> sumQueue = new LinkedList<>();
  15. queue.offer(root);
  16. sumQueue.offer(root.val);
  17. map.put(root, null);
  18. while (!queue.isEmpty()) {
  19. int size = queue.size();
  20. while (size > 0) {
  21. TreeNode curr = queue.poll();
  22. int val = sumQueue.poll();
  23. if (curr.left == null && curr.right == null && val == targetSum) {
  24. getPath(curr);
  25. }
  26. if (curr.left != null) {
  27. queue.offer(curr.left);
  28. sumQueue.offer(val + curr.left.val);
  29. map.put(curr.left, curr);
  30. }
  31. if (curr.right != null) {
  32. queue.offer(curr.right);
  33. sumQueue.offer(val + curr.right.val);
  34. map.put(curr.right, curr);
  35. }
  36. size--;
  37. }
  38. }
  39. return res;
  40. }
  41. /**
  42. * 寻找父亲节点路径
  43. * @param leaf
  44. */
  45. public void getPath(TreeNode leaf) {
  46. List<Integer> list = new ArrayList<>();
  47. while (leaf != null) {
  48. list.add(leaf.val);
  49. leaf = map.get(leaf);
  50. }
  51. Collections.reverse(list);
  52. res.add(list);
  53. }
  54. // dfs
  55. // 记录所有path路径
  56. List<List<Integer>> res2 = new LinkedList<>();
  57. public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
  58. dfs(root, targetSum, new LinkedList());
  59. return res2;
  60. }
  61. public void dfs(TreeNode root, int sum, LinkedList path) {
  62. if (root == null) {
  63. return;
  64. }
  65. sum -= root.val;
  66. path.offer(root.val);
  67. if (root.left == null && root.right == null && sum == 0) {
  68. res2.add(new LinkedList<>(path));
  69. }
  70. dfs(root.left, sum, path);
  71. dfs(root.right, sum, path);
  72. path.pollLast();
  73. }

437-路径总和 III

image.png
题意分析:

  • 输出和为 sum 的路径,该路径不一定包含根节点或叶子节点,只有和为 sum 的节点都可以,且不跨越根节点。

解题思路:

  • 结合路径总和II 解法,对所有节点递归调用 II 的逻辑,退出条件为 sum=0 即可。

注意点:

  • 递归的退出条件。

扩展:
代码:

  1. // 记录所有path路径
  2. List<List<Integer>> res = new LinkedList<>();
  3. public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
  4. if (root == null) {
  5. return res;
  6. }
  7. dfs(root, targetSum, new LinkedList<>());
  8. // 由于总和不一定包括根节点,对左右孩子节点也要递归遍历
  9. pathSum(root.left, targetSum);
  10. pathSum(root.right, targetSum);
  11. return res;
  12. }
  13. public void dfs(TreeNode root, int sum, LinkedList path) {
  14. if (root == null) {
  15. return;
  16. }
  17. sum -= root.val;
  18. path.offer(root.val);
  19. // 和路径总和II 问题的区别是这里不用判断节点是否是叶子节点
  20. if (sum == 0) {
  21. res.add(new LinkedList<>(path));
  22. }
  23. dfs(root.left, sum, path);
  24. dfs(root.right, sum, path);
  25. path.pollLast();
  26. }

687-最长同值路径(?)

image.png
题意分析:

  • 树中最长的包含相同 val 的节点,路径可以包含根节点。

解题思路:

  • dfs 后序遍历,计算每个节点的左右最大同值数量。

注意点:
扩展:
代码:

  1. int res = 0;
  2. public int longestUnivaluePath(TreeNode root) {
  3. longestPath(root);
  4. return res;
  5. }
  6. public int longestPath(TreeNode root){
  7. if (root == null) {
  8. return 0;
  9. }
  10. int left = longestPath(root.left);
  11. int right = longestPath(root.right);
  12. if (root.left != null && root.val == root.left.val) {
  13. left++;
  14. } else {
  15. left = 0;
  16. }
  17. if (root.right != null && root.val == root.right.val) {
  18. right++;
  19. } else {
  20. right = 0;
  21. }
  22. // 打印当前root节点的左右子树值都最好。
  23. // System.out.println(root.val + ": left = " + left +"; right = " + right);
  24. // 如果根节点的左右子树res都为1,则表示根节点和左右节点的值 val 相等。
  25. res = Math.max(res, left + right);
  26. // 返回左右子树的最长同值路径
  27. return Math.max(left, right);
  28. }

124-二叉树中的最大路径和(?)

image.png
image.png
题意分析:

  • 二叉树中节点包含负数情况下最大的路径和,可以经过或不经过根节点。

解题思路:

  • dfs 遍历分别计算每个节点的左右子树的最大路径和。

注意点:
扩展:
代码:

  1. int maxSum = Integer.MIN_VALUE;
  2. public int maxPathSum(TreeNode root) {
  3. maxPath(root);
  4. return maxSum;
  5. }
  6. public int maxPath(TreeNode root) {
  7. if (root == null) {
  8. return 0;
  9. }
  10. // 如果某个路径小于0,则改路径是无增益路径
  11. int left = Math.max(maxPath(root.left), 0);
  12. int right = Math.max(maxPath(root.right), 0);
  13. System.out.println(root.val + ": left = " + left +"; right = " + right);
  14. maxSum = Math.max(maxSum, left + right + root.val);
  15. return root.val + Math.max(left, right);
  16. }

988-从叶节点开始的最小字符串

image.png
题意分析:

  • 从所有叶子节点到根节点的路径中,找到字符串值最小的字符串。

解题思路:

  • 类似 路径总和II 问题思路,找到所有路径,比较路径字符串大小。

注意点:
扩展:
代码:

  1. String maxStr = "~";
  2. public String smallestFromLeaf(TreeNode root) {
  3. dfs(root, new StringBuilder());
  4. return maxStr;
  5. }
  6. public void dfs(TreeNode root, StringBuilder path) {
  7. if (root == null) {
  8. return;
  9. }
  10. path.append((char)('a' + root.val));
  11. if (root.left == null && root.right == null){
  12. path.reverse();
  13. String tmp = path.toString();
  14. path.reverse();
  15. if (tmp.compareTo(maxStr) < 0 ) {
  16. maxStr = tmp;
  17. }
  18. }
  19. dfs(root.left, path);
  20. dfs(root.right, path);
  21. // 回溯,删除 path 中最后一个节点,类似 路径总和II 问题思路
  22. path.deleteCharAt(path.length() - 1);
  23. }

前缀树

208-实现前缀树

image.png
题意分析:

  • 前缀树的新增、查找和前缀判断功能。

解题思路:

  • 定义前缀树,清楚前缀树的属性功能实现就相对容易。

注意点:

  • search() 方法实现的return逻辑,需要判断字符是否是前缀的特殊情况。

扩展:
代码:

  1. public class TreeTrieImpl_208 {
  2. TrieNode root;
  3. class TrieNode {
  4. int pass;
  5. int end;
  6. TrieNode[] childs;
  7. public TrieNode() {
  8. pass = 0;
  9. end = 0;
  10. childs = new TrieNode[26];
  11. }
  12. }
  13. public TreeTrieImpl_208() {
  14. root = new TrieNode();
  15. }
  16. public void insert(String word) {
  17. TrieNode node = root;
  18. int idx;
  19. char[] chs = word.toCharArray();
  20. for (int i = 0; i < chs.length; i++) {
  21. idx = chs[i] - 'a';
  22. if (node.childs[idx] == null) {
  23. node.childs[idx] = new TrieNode();
  24. }
  25. node.childs[idx].pass++;
  26. node = node.childs[idx];
  27. }
  28. node.end++;
  29. }
  30. public boolean search(String word) {
  31. TrieNode node = root;
  32. int idx;
  33. char[] chs = word.toCharArray();
  34. for (int i = 0; i < chs.length; i++) {
  35. idx = chs[i] - 'a';
  36. if (node.childs[idx] == null) {
  37. return false;
  38. }
  39. node = node.childs[idx];
  40. }
  41. // 不能直接返回ture,比如'apple'串中搜索'app',要判断是否结束字符
  42. return node.end != 0;
  43. }
  44. public boolean startsWith(String prefix) {
  45. TrieNode node = root;
  46. int idx;
  47. char[] chs = prefix.toCharArray();
  48. for (int i = 0; i < chs.length; i++) {
  49. idx = chs[i] - 'a';
  50. if (node.childs[idx] == null) {
  51. return false;
  52. }
  53. node = node.childs[idx];
  54. }
  55. return true;
  56. }
  57. public static void main(String[] args) {
  58. TreeTrieImpl_208 treeTrieImpl208 = new TreeTrieImpl_208();
  59. treeTrieImpl208.insert("apple");
  60. System.out.println(treeTrieImpl208.search("apple")); // 返回 True
  61. System.out.println(treeTrieImpl208.search("app")); // 返回 False
  62. System.out.println(treeTrieImpl208.startsWith("app")); // 返回 True
  63. treeTrieImpl208.insert("app");
  64. System.out.println(treeTrieImpl208.search("app")); // 返回 True
  65. }
  66. }

648-单词替换

image.png
题意分析:

  • 如果一个单词在前缀词字典中存在前缀词,则用前缀词替换。

解题思路:

  • 选择字典还是句子作为前缀树是关键,然后判断前缀是否存在。

注意点:
扩展:
代码:

  1. public class WordReplace_648 {
  2. TrieNode root = new TrieNode();
  3. public void insert(String word) {
  4. TrieNode node = root;
  5. int idx;
  6. char[] chs = word.toCharArray();
  7. for (int i = 0; i < chs.length; i++) {
  8. idx = chs[i] - 'a';
  9. if (node.childs[idx] == null) {
  10. node.childs[idx] = new TrieNode();
  11. }
  12. node.pass++;
  13. node = node.childs[idx];
  14. }
  15. node.end++;
  16. }
  17. public String shortestPrefix(String word) {
  18. TrieNode node = root;
  19. int idx;
  20. char[] chs = word.toCharArray();
  21. StringBuilder sb = new StringBuilder();
  22. for (int i = 0; i < chs.length; i++) {
  23. if (node.end != 0) { // 找到了存在的前缀
  24. break;
  25. }
  26. idx = chs[i] - 'a';
  27. if (node.childs[idx] == null) {
  28. return "";
  29. }
  30. sb.append(chs[i]);
  31. node = node.childs[idx];
  32. }
  33. return sb.toString();
  34. }
  35. public String replaceWords(List<String> dictionary, String sentence) {
  36. String[] strs = sentence.split(" ");
  37. // 将 dictionary 中单词构建成前缀树
  38. for (int i = 0; i < dictionary.size(); i++) {
  39. insert(dictionary.get(i));
  40. }
  41. StringBuilder sb = new StringBuilder();
  42. for (int i = 0; i < strs.length; i++) {
  43. String prefix = shortestPrefix(strs[i]);
  44. if (!prefix.isEmpty()) {
  45. sb.append(prefix);
  46. } else {
  47. sb.append(strs[i]);
  48. }
  49. if (i != strs.length -1) {
  50. sb.append(" ");
  51. }
  52. }
  53. return sb.toString();
  54. }
  55. public static void main(String[] args) {
  56. WordReplace_648 obj = new WordReplace_648();
  57. List<String> dictionary = new ArrayList<>(Arrays.asList("cat","bat","rat"));
  58. String sentence = "the cattle was rattled by the battery";
  59. System.out.println(obj.replaceWords(dictionary, sentence));
  60. }
  61. }

211-添加与搜索

image.png
题意分析:

  • 前缀树的应用,向前缀树中添加字符串,然后查询prefix前缀是否存在,也存在模式匹配的前缀。

解题思路:

  • 构建前缀树,然后采用 dfs + 回溯 方式遍历。

注意点:

  • dfs 遍历添加参数 pattern 和 idx 表示递归体在向前遍历。这种递归走向还不是特别容易理解。

扩展:
代码:

  1. public class WordDictionary {
  2. TrieNode root;
  3. public WordDictionary() {
  4. root = new TrieNode();
  5. }
  6. public void addWord(String word) {
  7. if (word == null) {
  8. return;
  9. }
  10. char[] chs = word.toCharArray();
  11. TrieNode node = root;
  12. node.pass++;
  13. int idx;
  14. // 遍历每一个字符,两种选择:有路径或者没有路径
  15. for (int i = 0; i < chs.length; i++) {
  16. idx = chs[i] - 'a';
  17. if (node.childs[idx] == null) {
  18. // 没有路径则新建路径
  19. node.childs[idx] = new TrieNode();
  20. }
  21. node = node.childs[idx];
  22. node.pass++;
  23. }
  24. node.end++;
  25. }
  26. public boolean search(String word) {
  27. return hasKeyWithPattern(root, word, 0);
  28. }
  29. private boolean hasKeyWithPattern(TrieNode node, String pattern, int idx) {
  30. if (node == null) {
  31. return false;
  32. }
  33. if (idx == pattern.length()) {
  34. return node.end != 0;
  35. }
  36. char ch = pattern.charAt(idx);
  37. if (ch == '.') {
  38. // '.' 符号则遍历所有子树
  39. for (int i = 0; i < node.childs.length; i++) {
  40. if (node.childs[i] != null) {
  41. if (hasKeyWithPattern(node.childs[i], pattern, idx + 1))
  42. return true;
  43. }
  44. }
  45. } else {
  46. return hasKeyWithPattern(node.childs[ch - 'a'], pattern, idx + 1);
  47. }
  48. return false;
  49. }
  50. public static void main(String[] args) {
  51. WordDictionary wordDictionary = new WordDictionary();
  52. wordDictionary.addWord("at");
  53. wordDictionary.addWord("and");
  54. wordDictionary.addWord("an");
  55. wordDictionary.addWord("add");
  56. System.out.println(wordDictionary.search("a")); // return False
  57. System.out.println(wordDictionary.search(".at")); // return False
  58. wordDictionary.addWord("bat");
  59. System.out.println(wordDictionary.search(".at")); // return true
  60. }
  61. }

677-键值映射

image.png
题意分析:

  • 首先是判断前缀prefix是否是存在字符串的前缀,如果是,求所有以 prefix 为前缀的字符的 val 的和。

解题思路:

  • 熟悉前缀树的基本操作就比较容易,首先构建前缀树,然后判断前缀 prefix 是否是合法的(即 getNode 判断前缀是否存在对应的路径节点),最后从 prefix 的路径结束节点开始 dfs 遍历,求所有叶子节点的 val 和。

注意点:
扩展:
代码:

  1. public class TrieMapSum_677 {
  2. TrieNode root;
  3. public TrieMapSum_677() {
  4. root = new TrieNode();
  5. }
  6. public void insert(String key, int val) {
  7. TrieNode node = root;
  8. node.pass++;
  9. int idx;
  10. char[] chs = key.toCharArray();
  11. for (int i = 0; i < chs.length; i++) {
  12. idx = chs[i] - 'a';
  13. if (node.childs[idx] == null) {
  14. node.childs[idx] = new TrieNode();
  15. }
  16. node = node.childs[idx];
  17. node.pass++;
  18. }
  19. node.end++;
  20. node.val = val;
  21. }
  22. int sum = 0;
  23. public int sum(String prefix) {
  24. sum = 0;
  25. TrieNode node = getNode(prefix);
  26. if (node == null) {
  27. return 0;
  28. }
  29. // 以node为根节点递归遍历,求其子树下所有叶子节点的和
  30. dfs(node);
  31. return sum;
  32. }
  33. public void dfs(TrieNode node) {
  34. if (node == null) {
  35. return;
  36. }
  37. if (node.end != 0) {
  38. sum += node.val;
  39. }
  40. // 树的先序遍历。(多叉树)
  41. for (int i = 0; i < node.childs.length; i++) {
  42. if (node.childs[i] != null) {
  43. dfs(node.childs[i]);
  44. }
  45. }
  46. }
  47. public TrieNode getNode(String prefix) {
  48. TrieNode node = root;
  49. int idx;
  50. char[] chs = prefix.toCharArray();
  51. for (int i = 0; i < chs.length; i++) {
  52. idx = chs[i] - 'a';
  53. if (node.childs[idx] == null) {
  54. return null;
  55. }
  56. node = node.childs[idx];
  57. }
  58. return node;
  59. }
  60. public static void main(String[] args) {
  61. TrieMapSum_677 mapSum = new TrieMapSum_677();
  62. mapSum.insert("apple", 3);
  63. System.out.println(mapSum.sum("ap")); // return 3 (apple = 3)
  64. mapSum.insert("app", 2);
  65. System.out.println(mapSum.sum("ap")); // return 5 (apple + app = 3 + 2 = 5)
  66. }
  67. }

未分类

543-二叉树的直径

image.png
题意分析:

  • 直径定义:无非就是判断根节点的左右子树深度,两者和即为直径。

解题思路:

  • 后序遍历,计算每个节点的左右子树深度。

注意点:
扩展:
代码:

  1. int res = 0;
  2. public int diameterOfBinaryTree(TreeNode root) {
  3. diameter(root);
  4. return res;
  5. }
  6. public int diameter(TreeNode root) {
  7. if (root == null) {
  8. return 0;
  9. }
  10. int left = diameter(root.left);
  11. int right = diameter(root.right);
  12. if (root.left != null) {
  13. left++;
  14. } else {
  15. left = 0;
  16. }
  17. if (root.right != null) {
  18. right++;
  19. } else {
  20. right = 0;
  21. }
  22. System.out.println(root.val + ": left = " + left +"; right = " + right);
  23. // 统计最长路径。如果是统计节点: left + right + 1
  24. res = Math.max(res, left + right);
  25. return Math.max(left, right);
  26. }

101-对称二叉树

image.png
题意分析:

  • 判断 p.left 和 q.right 元素是否是一致的

解题思路:

  • bfs 遍历。每次入队两个元素,即对称的两个节点元素,判断是否对称。
  • dfs 遍历。递归判断两个节点。

注意点:
扩展:
代码:

  1. /**
  2. * bfs 遍历
  3. * @param root
  4. * @return
  5. */
  6. public boolean isSymmetric(TreeNode root) {
  7. Queue<TreeNode> queue = new LinkedList<>();
  8. queue.offer(root);
  9. queue.offer(root);
  10. while (!queue.isEmpty()) {
  11. TreeNode p = queue.poll();
  12. TreeNode q = queue.poll();
  13. if (p == null && q == null) {
  14. continue;
  15. }
  16. if (p == null || q == null || p.val != q.val) {
  17. return false;
  18. }
  19. queue.offer(p.left);
  20. queue.offer(q.right);
  21. queue.offer(p.right);
  22. queue.offer(q.left);
  23. }
  24. return true;
  25. }
  26. /**
  27. * dfs 遍历
  28. * @param root
  29. * @return
  30. */
  31. public boolean isSymmetric2(TreeNode root) {
  32. return check(root, root);
  33. }
  34. private boolean check(TreeNode p, TreeNode q) {
  35. if (p == null && q == null) {
  36. return true;
  37. }
  38. if (p == null || q == null)
  39. return false;
  40. return (p.val == q.val) && check(p.left, q.right) && check(p.right, q.left);
  41. }

104-二叉树的最大深度

image.png
题意分析:

  • 树的最大深度。

解题思路:

  • bfs 最直观,遍历每一层的节点。
  • dfs 后序(自底向上)遍历记录每个节点的左右最大深度。

注意点:
扩展:
代码:

  1. /**
  2. * 树的深度优先遍历
  3. * @param root
  4. * @return
  5. */
  6. public int maxDepth(TreeNode root) {
  7. if (root == null) {
  8. return 0;
  9. }
  10. int leftDepth = maxDepth(root.left);
  11. int rightDepth = maxDepth(root.right);
  12. return Math.max(leftDepth, rightDepth) + 1;
  13. }
  14. public int maxDepth2(TreeNode root) {
  15. if (root == null) {
  16. return 0;
  17. }
  18. Queue<TreeNode> queue = new LinkedList<>();
  19. queue.offer(root);
  20. int depth = 0;
  21. while (!queue.isEmpty()) {
  22. // size 记录每一层节点的数量,一层节点出队列完毕后深度+1
  23. int size = queue.size();
  24. while (size > 0 ) {
  25. TreeNode node = queue.poll();
  26. if (node.left != null) {
  27. queue.offer(node.left);
  28. }
  29. if (node.right != null) {
  30. queue.offer(node.right);
  31. }
  32. size--;
  33. }
  34. depth++;
  35. }
  36. return depth;
  37. }

111-二叉树的最小深度

image.png
题意分析:

  • 和最大深度差不多。

解题思路:

  • bfs 遍历最简单,遇到叶子节点直接返回深度。

注意点:
扩展:
代码:

  1. int res = Integer.MAX_VALUE;
  2. public int minDepth(TreeNode root) {
  3. if (root == null) {
  4. return 0;
  5. }
  6. Queue<TreeNode> queue = new LinkedList<>();
  7. queue.offer(root);
  8. int depth = 0;
  9. while (!queue.isEmpty()) {
  10. // size 记录每一层节点的数量,一层节点出队列完毕后深度+1
  11. int size = queue.size();
  12. while (size > 0 ) {
  13. TreeNode node = queue.poll();
  14. if (node.left == null && node.right == null) {
  15. return ++depth;
  16. }
  17. if (node.left != null) {
  18. queue.offer(node.left);
  19. }
  20. if (node.right != null) {
  21. queue.offer(node.right);
  22. }
  23. size--;
  24. }
  25. depth++;
  26. }
  27. return depth;
  28. }

987-二叉树的垂序遍历

image.png
题意分析:

  • 根据每个节点 x/y 值对树节点进行遍历。

解题思路:

  • 逻辑思路比较清晰。
    • 先遍历树,计算每个节点的 x 和 y 值。
    • 然后对所有节点排序,自定义排序器,分别按 y、x、val 升序遍历。
    • 分组输出节点元素。

注意点:
扩展:
代码:

  1. /**
  2. * 题目考查的知识点:
  3. * 1、二叉树的遍历;
  4. * 2、Java 比较器的实现;
  5. * 3、数组/List 的遍历技巧。
  6. * List[1,2,3,3,3,4,4,5] = 统计每个单词出现的次数,并放在不同集合中。[[1],[2],[3,3,3],[4,4],[5]]
  7. */
  8. public class VerticalTraversal_987 {
  9. List<VerticalNode> nodeList = new ArrayList<>();
  10. public List<List<Integer>> verticalTraversal(TreeNode root) {
  11. // 遍历树节点,获得每个节点的 [x, y , val] 组值
  12. dfs(root, 0, 0);
  13. // System.out.println("----------排序前--------");
  14. // for (VerticalNode node : nodeList) {
  15. // System.out.println(node.val + ": " + node.x + ": " + node.y);
  16. // }
  17. // 排序,先根据列 y 升序排序,如果 y 相同,则按照 val 升序排序
  18. Collections.sort(nodeList, new Comparator<VerticalNode>() {
  19. @Override
  20. public int compare(VerticalNode o1, VerticalNode o2) {
  21. if (o1.y != o2.y) {
  22. return o1.y - o2.y; // 按 y 升序
  23. } else if (o1.x != o2.x) {
  24. return o1.x - o2.x; // 按 x 升序
  25. } else {
  26. return o1.val - o2.val;
  27. }
  28. }
  29. });
  30. System.out.println("----------排序后--------");
  31. for (VerticalNode node : nodeList) {
  32. System.out.println(node.val + ": " + node.x + ": " + node.y);
  33. }
  34. // 排序后输出结果
  35. List<List<Integer>> retList = new ArrayList<>();
  36. List<Integer> list = new ArrayList<>();
  37. int last_y = Integer.MIN_VALUE;
  38. // 记录有多少组不同 y 元素
  39. int size = -1;
  40. // 此时遍历可忽略 行x 的影响,因为 x 是有序的,List 也是有序的,根据 y 遍历输出 val 的值即可。
  41. for (VerticalNode node : nodeList) {
  42. if (node.y != last_y) {
  43. size++;
  44. last_y = node.y;
  45. list.add(node.val);
  46. retList.add(list);
  47. list = new ArrayList<>();
  48. } else {
  49. retList.get(size).add(node.val);
  50. }
  51. }
  52. System.out.println(retList);
  53. return retList;
  54. }
  55. public void dfs(TreeNode root, int x, int y) {
  56. if (root == null) {
  57. return;
  58. }
  59. nodeList.add(new VerticalNode(root.val, x, y));
  60. // 对左右子树递归遍历
  61. dfs(root.left, x+1, y-1);
  62. dfs(root.right, x+1, y+1);
  63. }
  64. /**
  65. * 1
  66. * 2 3
  67. * 4 5
  68. */
  69. public static void main(String[] args) {
  70. VerticalTraversal_987 obj = new VerticalTraversal_987();
  71. System.out.println(obj.verticalTraversal(Utils.createTree2()));
  72. }
  73. class VerticalNode {
  74. int val;
  75. int x;
  76. int y;
  77. public VerticalNode(int val, int x, int y) {
  78. this.val = val;
  79. this.x = x;
  80. this.y = y;
  81. }
  82. }
  83. }

模版

题意分析:
解题思路:
注意点:
扩展:
代码: