二叉树

题型5:LCA最近公共祖先

236.二叉树的最近公共祖先(中等)

解题思路:
核心点在于:

  1. 如果当前节点 node 的左右子树各含有 p 或 q 节点,那么 node节点则为 p 和 q 节点的最近公共祖先
  2. 如果当前节点 node 等于 p 或 q 节点,那么当前节点 node 为 p 和 q 节点的最近公共祖先

    递归解法一
    1. class Solution {
    2. private TreeNode lca;
    3. public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    4. dfs(root, p, q);
    5. return lca;
    6. }
    7. private int dfs(TreeNode root, TreeNode p, TreeNode q) {
    8. if(root == null) {
    9. return 0;
    10. }
    11. int leftContains = dfs(root.left, p, q);
    12. if(lca != null) {
    13. return 2;
    14. }
    15. int rightContains = dfs(root.right, p, q);
    16. // 剪枝
    17. if(lca != null) {
    18. return 2;
    19. }
    20. int rootContains = 0;
    21. if(root == p || root == q) {
    22. rootContains = 1;
    23. }
    24. if(rootContains == 0 && leftContains == 1 && rightContains == 1) {
    25. lca = root;
    26. }
    27. if(rootContains == 1 && (leftContains == 1 || rightContains == 1)) {
    28. lca = root;
    29. }
    30. return rootContains + leftContains + rightContains;
    31. }
    32. }

递归解法二
  1. class Solution {
  2. public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
  3. if(root == null) {
  4. return null;
  5. }
  6. if(root == p || root == q) {
  7. return root;
  8. }
  9. TreeNode lNode = lowestCommonAncestor(root.left, p, q);
  10. TreeNode rNode = lowestCommonAncestor(root.right, p, q);
  11. if(lNode != null && rNode != null) {
  12. return root;
  13. } else if(lNode == null) {
  14. return rNode;
  15. } else {
  16. return lNode;
  17. }
  18. }
  19. }

剑指Offer 68 - I.二叉搜索树的最近公共祖先(中等)

  1. class Solution {
  2. public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
  3. while(root != null) {
  4. if(p.val < root.val && root.val < q.val) {
  5. return root;
  6. } else if(p.val < root.val && q.val < root.val) {
  7. root = root.left;
  8. } else if(p.val > root.val && q.val > root.val) {
  9. root = root.right;
  10. } else {
  11. return root;
  12. }
  13. }
  14. return null;
  15. }
  16. }

题型6:二叉树转单、双、循环链表

114.二叉树展开为链表(中等)

哨兵节点 + 前序遍历
  1. class Solution {
  2. // 伪头结点
  3. private TreeNode dumyHead = new TreeNode();
  4. // 核心点:tail 要设置为 全局变量
  5. private TreeNode tail = dumyHead;
  6. public void flatten(TreeNode root) {
  7. if(root == null) {
  8. return;
  9. }
  10. // 先序遍历
  11. TreeNode left = root.left;
  12. TreeNode right = root.right;
  13. // 往伪头结点插入
  14. tail.right = root;
  15. tail = root;
  16. root.left = null;
  17. flatten(left);
  18. flatten(right);
  19. }
  20. }

后序遍历
  1. /**
  2. * Definition for a binary tree node.
  3. * public class TreeNode {
  4. * int val;
  5. * TreeNode left;
  6. * TreeNode right;
  7. * TreeNode() {}
  8. * TreeNode(int val) { this.val = val; }
  9. * TreeNode(int val, TreeNode left, TreeNode right) {
  10. * this.val = val;
  11. * this.left = left;
  12. * this.right = right;
  13. * }
  14. * }
  15. */
  16. class Solution {
  17. public void flatten(TreeNode root) {
  18. if(root == null) {
  19. return;
  20. }
  21. // 后序遍历
  22. flatten(root.left);
  23. flatten(root.right);
  24. TreeNode left = root.left;
  25. TreeNode right = root.right;
  26. // 将左子树作为右子树
  27. root.right = left;
  28. root.left = null;
  29. // 将原右子树接至当前右子树的最右端
  30. TreeNode p = root;
  31. while(p.right != null) {
  32. p = p.right;
  33. }
  34. p.right = right;
  35. }
  36. }

面试题17.12. BiNode(中等)

  1. class Solution {
  2. private TreeNode dumyHead = new TreeNode();
  3. private TreeNode tail = dumyHead;
  4. public TreeNode convertBiNode(TreeNode root) {
  5. inorder(root);
  6. return dumyHead.right;
  7. }
  8. private void inorder(TreeNode root) {
  9. if(root == null) {
  10. return;
  11. }
  12. inorder(root.left);
  13. TreeNode right = root.right;
  14. tail.right = root;
  15. tail = root;
  16. tail.left = null;
  17. inorder(right);
  18. }
  19. }

剑指Offer 36.二叉搜索树与双向链表(中等)

  1. class Solution {
  2. private Node head = new Node();
  3. private Node tail = head;
  4. public Node treeToDoublyList(Node root) {
  5. if(root == null) {
  6. return null;
  7. }
  8. // 中序遍历
  9. inorder(root);
  10. tail.right = head.right;
  11. head.right.left = tail;
  12. return head.right;
  13. }
  14. private void inorder(Node node) {
  15. if(node == null) {
  16. return;
  17. }
  18. inorder(node.left);
  19. tail.right = node;
  20. node.left = tail;
  21. tail = node;
  22. inorder(node.right);
  23. }
  24. }

面试题04.03.特定深度节点链表(中等)

队列-层次遍历
  1. class Solution {
  2. public ListNode[] listOfDepth(TreeNode tree) {
  3. if(tree == null) {
  4. return new ListNode[0];
  5. }
  6. // 层次遍历
  7. Queue<TreeNode> queue = new LinkedList<>();
  8. List<ListNode> res = new ArrayList<>();
  9. queue.offer(tree);
  10. while(!queue.isEmpty()) {
  11. int size = queue.size();
  12. ListNode dumyHeadNode = new ListNode(), tmp = dumyHeadNode;
  13. for(int i = 0; i < size; i++) {
  14. TreeNode node = queue.poll();
  15. tmp.next = new ListNode(node.val);
  16. tmp = tmp.next;
  17. if(node.left != null) {
  18. queue.offer(node.left);
  19. }
  20. if(node.right != null) {
  21. queue.offer(node.right);
  22. }
  23. }
  24. res.add(dumyHeadNode.next);
  25. }
  26. return res.toArray(new ListNode[0]);
  27. }
  28. }

递归-深度优先遍历
  1. class Solution {
  2. public ListNode[] listOfDepth(TreeNode tree) {
  3. List<ListNode> res = new ArrayList<>();
  4. dfs(tree, res, 0);
  5. return res.toArray(new ListNode[0]);
  6. }
  7. private void dfs(TreeNode root, List<ListNode> res, int level) {
  8. if(root == null) {
  9. return;
  10. }
  11. ListNode newNode = new ListNode(root.val);
  12. if(res.size() <= level) {
  13. res.add(newNode);
  14. } else {
  15. // 头插法
  16. newNode.next = res.get(level);
  17. res.set(level, newNode);
  18. }
  19. // 往右子树向下遍历
  20. dfs(root.right, res, level + 1);
  21. dfs(root.left, res, level + 1);
  22. }
  23. }

题型7:按照遍历结果反向构建二叉树

105.从前序与中序遍历序列构造二叉树(中等)

递归

解题思路:

  1. 在前序遍历数组中,能明确根节点的值。
  2. 结合步骤一,在中序遍历数组中,能明确左右子树的节点范围。

    1. class Solution {
    2. public TreeNode buildTree(int[] preorder, int[] inorder) {
    3. // 递归
    4. return myBuildTree(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1);
    5. }
    6. private TreeNode myBuildTree(int[] preorder, int i, int j, int[] inorder, int p, int r) {
    7. if(i > j) {
    8. return null;
    9. }
    10. int val = preorder[i];
    11. TreeNode root = new TreeNode(val);
    12. // 找出根节点在中序遍历数组中的下标
    13. int rIdx = p;
    14. while(rIdx <= r && inorder[rIdx] != val) {
    15. rIdx++;
    16. }
    17. // 计算根节点的左子树的节点大小
    18. int leftTreeSize = rIdx - p;
    19. TreeNode left = myBuildTree(preorder, i + 1, i + leftTreeSize, inorder, p, rIdx - 1);
    20. TreeNode right = myBuildTree(preorder, i + leftTreeSize + 1, j, inorder, rIdx + 1, r);
    21. root.left = left;
    22. root.right = right;
    23. return root;
    24. }
    25. }

889.根据前序和后序遍历构造二叉树(中等)

  1. class Solution {
  2. public TreeNode constructFromPrePost(int[] pre, int[] post) {
  3. return buildTree(pre, 0, pre.length - 1, post, 0, post.length - 1);
  4. }
  5. private TreeNode buildTree(int[] pre, int preStart, int preEnd, int[] post, int postStart, int postEnd) {
  6. if(preStart > preEnd) {
  7. return null;
  8. }
  9. TreeNode root = new TreeNode(pre[preStart]);
  10. if(preStart == preEnd) {
  11. return root;
  12. }
  13. int idx = postStart;
  14. while(post[idx] != pre[preStart + 1]) {
  15. idx++;
  16. }
  17. int leftTreeSize = idx - postStart;
  18. TreeNode left = buildTree(pre, preStart + 1, preStart + 1 + leftTreeSize, post, postStart, idx);
  19. TreeNode right = buildTree(pre, preStart + 1 + leftTreeSize + 1, preEnd, post, idx + 1, postEnd -1);
  20. root.left = left;
  21. root.right = right;
  22. return root;
  23. }
  24. }

106.从中序与后序遍历序列构造二叉树(中等)

  1. class Solution {
  2. public TreeNode buildTree(int[] inorder, int[] postorder) {
  3. return myBuildTree(inorder, 0, inorder.length - 1, postorder, 0, postorder.length - 1);
  4. }
  5. private TreeNode myBuildTree(int[] inorder, int inStart, int inEnd, int[] postorder, int postStart, int postEnd) {
  6. if(inStart > inEnd) {
  7. return null;
  8. }
  9. int rootVal = postorder[postEnd];
  10. TreeNode root = new TreeNode(rootVal);
  11. if(postStart == postEnd) {
  12. return root;
  13. }
  14. int index = inStart;
  15. while(inorder[index] != rootVal) {
  16. index++;
  17. }
  18. int leftTreeSize = index - inStart;
  19. TreeNode left = myBuildTree(inorder, inStart, index - 1, postorder, postStart, postStart + leftTreeSize - 1);
  20. TreeNode right = myBuildTree(inorder, index + 1, inEnd, postorder, postStart + leftTreeSize, postEnd - 1);
  21. root.left = left;
  22. root.right = right;
  23. return root;
  24. }
  25. }

剑指Offer 33.二叉搜索树的后序遍历序列(中等)

解题思路:
二叉树前中后序遍历的组成结构:
前序:[ 根节点 | 左子树节点 | 右子树节点 ]
中序:[ 左子树节点 | 根节点 | 右子树节点 ]
后序:[ 左子树节点 | 右子树节点 | 根节点 ]

BST 特性:任意一个节点的值都比其左子树节点的值大,比其右子树节点的值小。

  1. class Solution {
  2. public boolean verifyPostorder(int[] postorder) {
  3. return cur(postorder, 0, postorder.length - 1);
  4. }
  5. private boolean cur(int[] postorder, int postStart, int postEnd) {
  6. if(postStart >= postEnd) {
  7. return true;
  8. }
  9. // 根节点值
  10. int rootVal = postorder[postEnd];
  11. int idx = postStart;
  12. // 遍历左子树的值,找出第一个比根节点大的值
  13. while(idx < postEnd && postorder[idx] < rootVal) {
  14. idx++;
  15. }
  16. // 判断右子树范围内的值,此范围内的值需要比根节点的值大
  17. for(int i = idx; i < postEnd; i++) {
  18. if(postorder[i] < rootVal) {
  19. return false;
  20. }
  21. }
  22. return cur(postorder, postStart, idx - 1) && cur(postorder, idx, postEnd - 1);
  23. }
  24. }

题型8:二叉树上的最长路径和

543.二叉树的直径(简单)

递归

解题思路:转换成求树深即可。即比较每个节点的左右子树深度之和,求最大值。

  1. class Solution {
  2. private int ans = 0;
  3. public int diameterOfBinaryTree(TreeNode root) {
  4. if(root == null) {
  5. return 0;
  6. }
  7. maxDepth(root);
  8. return ans;
  9. }
  10. private int maxDepth(TreeNode root) {
  11. if(root == null) {
  12. return 0;
  13. }
  14. int left = maxDepth(root.left);
  15. int right = maxDepth(root.right);
  16. int dia = left + right;
  17. ans = Math.max(ans, dia);
  18. return Math.max(left, right) + 1;
  19. }
  20. }

剑指Offer 34.二叉树中和为某一值的路径(中等)

  1. class Solution {
  2. public List<List<Integer>> pathSum(TreeNode root, int target) {
  3. List<List<Integer>> res = new ArrayList<>();
  4. dfs(root, res, new ArrayList<Integer>(), target, 0);
  5. return res;
  6. }
  7. private void dfs(TreeNode root, List<List<Integer>> res, List<Integer> path, int target, int pathSum) {
  8. if(root == null) {
  9. return;
  10. }
  11. pathSum += root.val;
  12. path.add(root.val);
  13. // 从树的根节点开始往下一直到叶节点所经过的节点形成一条路径,即必须是到叶子节点
  14. if(root.left == null && root.right == null && target == pathSum) {
  15. List<Integer> tmp = new ArrayList<>(path);
  16. res.add(tmp);
  17. path.remove(path.size() - 1);
  18. return;
  19. }
  20. dfs(root.left, res, path, target, pathSum);
  21. dfs(root.right, res, path, target, pathSum);
  22. path.remove(path.size() - 1);
  23. }
  24. }

124.二叉树中的最大路径和 (困难)

437. 路径总和III (困难)

堆和Trie

23.合并K个升序链表(困难) (例题)

解题思路:利用优先级队列来构造小顶堆,然后利用小顶堆的特性来实现升序链表

  1. class Solution {
  2. public ListNode mergeKLists(ListNode[] lists) {
  3. ListNode dumyHead = new ListNode(), tNode = dumyHead;
  4. // 利用优先级队列构造小顶堆
  5. PriorityQueue<ListNode> queue = new PriorityQueue<ListNode>((n1, n2) -> n1.val - n2.val);
  6. // 初始化小顶堆
  7. for(ListNode node : lists) {
  8. if(node != null) {
  9. queue.offer(node);
  10. }
  11. }
  12. while(!queue.isEmpty()) {
  13. ListNode node = queue.poll();
  14. tNode.next = node;
  15. tNode = node;
  16. if(node.next != null) {
  17. queue.offer(node.next);
  18. }
  19. }
  20. return dumyHead.next;
  21. }
  22. }

347.前K个高频元素(中等) (例题)

优先级队列 - 大顶堆

解题思路:

  1. 自定类记录数组中不重复元素的值以及其出现的次数。
  2. 使用 HashMap 计算数组中不重复元素的出现次数。
  3. 利用优先级队列构造大顶堆,通过大顶堆的特性,获取前 k 个高频元素。

    1. class Solution {
    2. class Node {
    3. private int val;
    4. private int count;
    5. public Node(int val, int count) {
    6. this.val = val;
    7. this.count = count;
    8. }
    9. }
    10. public int[] topKFrequent(int[] nums, int k) {
    11. // 利用优先级构造大顶堆
    12. PriorityQueue<Node> queue = new PriorityQueue<>((n1, n2) -> n2.count - n1.count);
    13. // 计算频率
    14. HashMap<Integer, Integer> map = new HashMap<>();
    15. for(int num : nums) {
    16. if(map.containsKey(num)) {
    17. map.put(num, map.get(num) + 1);
    18. } else {
    19. map.put(num, 1);
    20. }
    21. }
    22. // 初始化大顶堆
    23. for(Map.Entry<Integer, Integer> entry : map.entrySet()) {
    24. queue.offer(new Node(entry.getKey(), entry.getValue()));
    25. }
    26. int[] res = new int[k];
    27. for(int i = 0; i < k; i++) {
    28. res[i] = queue.poll().val;
    29. }
    30. return res;
    31. }
    32. }

295.数据流的中位数(困难)(例题)

  1. class MedianFinder {
  2. // 最小堆
  3. private PriorityQueue<Integer> minHeap;
  4. // 最大堆
  5. private PriorityQueue<Integer> maxHeap;
  6. /** initialize your data structure here. */
  7. public MedianFinder() {
  8. this.minHeap = new PriorityQueue<>((o1, o2) -> o1 - o2);
  9. this.maxHeap = new PriorityQueue<>((o1, o2) -> o2 - o1);
  10. }
  11. public void addNum(int num) {
  12. if(maxHeap.isEmpty() || num <= maxHeap.peek()) {
  13. maxHeap.add(num);
  14. } else {
  15. minHeap.add(num);
  16. }
  17. // 维持最大堆内元素个数永远只比最小堆内元素个数最多多一个元素。
  18. while(maxHeap.size() < minHeap.size()) {
  19. maxHeap.add(minHeap.poll());
  20. }
  21. while(minHeap.size() < maxHeap.size() - 1) {
  22. minHeap.add(maxHeap.poll());
  23. }
  24. }
  25. public double findMedian() {
  26. if(maxHeap.isEmpty()) {
  27. return 0;
  28. }
  29. if(maxHeap.size() > minHeap.size()) {
  30. return maxHeap.peek();
  31. } else {
  32. return (maxHeap.peek() + minHeap.peek()) / 2f;
  33. }
  34. }
  35. }

973.最接近原点的K个点(中等)

优先级队列-小顶堆

解题思路:

  1. 自定义一个类,存储数组 point 和到原点的距离 sqrt
  2. 使用优先级队列,构建小顶堆 minHeap,将数组 points 值全部加入到 minHeap 中
  3. 利用小顶堆的特性,poll 出 k 个元素,即为最接近原点的 k 个点

    1. class Solution {
    2. class Closest {
    3. private int[] point;
    4. private int sqrt;
    5. public Closest(int[] point, int sqrt) {
    6. this.point = point;
    7. this.sqrt = sqrt;
    8. }
    9. }
    10. public int[][] kClosest(int[][] points, int k) {
    11. // 小顶堆
    12. PriorityQueue<Closest> queue = new PriorityQueue<>((c1, c2) -> c1.sqrt - c2.sqrt);
    13. // 初始化小顶堆
    14. for(int i = 0; i < points.length; i++) {
    15. int[] point = points[i];
    16. int x = point[0];
    17. int y = point[1];
    18. int sqrt = x * x + y * y;
    19. queue.offer(new Closest(point, sqrt));
    20. }
    21. int[][] res = new int[k][2];
    22. for(int i = 0; i < k; i++) {
    23. Closest c = queue.poll();
    24. res[i] = c.point;
    25. }
    26. return res;
    27. }
    28. }

优先级队列-大顶堆

解题思路:

  • 自定义一个类,存储数组 point 和到原点的距离 sqrt 。
  • 使用优先级队列,构建大顶堆 maxHeap,先将数组 points 中 k 个元素加入到 maxHeap 中。
  • 然后遍历数组 points 中剩余的元素,比 maxHeap 堆顶元素小的,加入 maxHeap,并 poll 出堆顶元素,遍历完毕后,堆内元素即为最接近原点的 k 个点。

    1. class Solution {
    2. class Closest {
    3. private int[] point;
    4. private int sqrt;
    5. public Closest(int[] point, int sqrt) {
    6. this.point = point;
    7. this.sqrt = sqrt;
    8. }
    9. }
    10. public int[][] kClosest(int[][] points, int k) {
    11. // 构建大顶堆
    12. PriorityQueue<Closest> maxHeap = new PriorityQueue<>((c1, c2) -> c2.sqrt - c1.sqrt);
    13. // 初始化大顶堆
    14. for(int i = 0; i < k; i++) {
    15. int[] point = points[i];
    16. int sqrt = sqrt(point);
    17. maxHeap.offer(new Closest(point, sqrt));
    18. }
    19. for(int i = k; i < points.length; i++) {
    20. int[] point = points[i];
    21. int sqrt = sqrt(point);
    22. if(sqrt < maxHeap.peek().sqrt) {
    23. maxHeap.offer(new Closest(point, sqrt));
    24. maxHeap.poll();
    25. }
    26. }
    27. int[][] res = new int[k][2];
    28. for(int i = 0; i < k; i++) {
    29. Closest c = maxHeap.poll();
    30. res[i] = c.point;
    31. }
    32. return res;
    33. }
    34. private int sqrt(int[] point) {
    35. int x = point[0];
    36. int y = point[1];
    37. return x * x + y * y;
    38. }
    39. }

313.超级丑数(中等)

优先级队-最小堆

解题思路:

  1. class Solution {
  2. public int nthSuperUglyNumber(int n, int[] primes) {
  3. PriorityQueue<Long> minHeap = new PriorityQueue<>();
  4. // 初始化最小堆
  5. long res = 1;
  6. for(int i = 1; i < n; i++) {
  7. for(int prime : primes) {
  8. minHeap.add(prime * res);
  9. }
  10. res = minHeap.poll();
  11. while(!minHeap.isEmpty() && res == minHeap.peek()) {
  12. minHeap.poll();
  13. }
  14. }
  15. return (int) res;
  16. }
  17. }


Trie

208.实现Trie (前缀树)(中等) 标准Trie树

  1. class Trie {
  2. class TreeNode {
  3. private TreeNode[] children;
  4. private boolean isEnd;
  5. public TreeNode() {
  6. this.children = new TreeNode[26];
  7. this.isEnd = false;
  8. }
  9. }
  10. private TreeNode root;
  11. /** Initialize your data structure here. */
  12. public Trie() {
  13. root = new TreeNode();
  14. }
  15. /** Inserts a word into the trie. */
  16. public void insert(String word) {
  17. char[] array = word.toCharArray();
  18. TreeNode p = root;
  19. for(char c : array) {
  20. int index = hash(c);
  21. if(p.children[index] == null) {
  22. p.children[index] = new TreeNode();
  23. }
  24. p = p.children[index];
  25. }
  26. p.isEnd = true;
  27. }
  28. /** Returns if the word is in the trie. */
  29. public boolean search(String word) {
  30. TreeNode p = searchPrefix(word);
  31. return p != null && p.isEnd;
  32. }
  33. /** Returns if there is any word in the trie that starts with the given prefix. */
  34. public boolean startsWith(String prefix) {
  35. return searchPrefix(prefix) != null;
  36. }
  37. private TreeNode searchPrefix(String word) {
  38. char[] array = word.toCharArray();
  39. TreeNode p = root;
  40. for(char c : array) {
  41. int index = hash(c);
  42. if(p.children[index] == null) {
  43. return null;
  44. }
  45. p = p.children[index];
  46. }
  47. return p;
  48. }
  49. private int hash(char c) {
  50. return c - 'a';
  51. }
  52. }
  53. /**
  54. * Your Trie object will be instantiated and called as such:
  55. * Trie obj = new Trie();
  56. * obj.insert(word);
  57. * boolean param_2 = obj.search(word);
  58. * boolean param_3 = obj.startsWith(prefix);
  59. */

以下为选做题目:

面试题17.17.多次搜索(中等) 标准AC自动机,不过写AC自动机太复杂,Trie树搞定

212.单词搜索II(困难)