题目描述(简单难度)

297、Serialize and Deserialize Binary Tree

Serialization is the process of converting a data structure or object into a sequence of bits so that it can be stored in a file or memory buffer, or transmitted across a network connection link to be reconstructed later in the same or another computer environment.

Design an algorithm to serialize and deserialize a binary tree. There is no restriction on how your serialization/deserialization algorithm should work. You just need to ensure that a binary tree can be serialized to a string and this string can be deserialized to the original tree structure.

Example:

  1. You may serialize the following tree:
  2. 1
  3. / \
  4. 2 3
  5. / \
  6. 4 5
  7. as "[1,2,3,null,null,4,5]"

Clarification: The above format is the same as how LeetCode serializes a binary tree. You do not necessarily need to follow this format, so please be creative and come up with different approaches yourself.

Note: Do not use class member/global/static variables to store states. Your serialize and deserialize algorithms should be stateless.

提供两个方法,一个方法将二叉树序列化为一个字符串,另一个方法将序列化的字符串还原为二叉树。

解法一

来个偷懒的方法,我们知道通过先序遍历和中序遍历可以还原一个二叉树。144 题 的先序遍历,94 题 的中序遍历,105 题 的通过先序遍历和中序遍历还原二叉树。

上边主要的代码都有了,我们还需要将先序遍历和中序遍历的结果转为字符串,以及从字符串还原先序遍历和中序遍历的结果。

listtoString 方法,它会把 list 转成 "[1, 2, 3, 5]"这样类似的字符串。所以把这个字符串还原为 list 的时候,我们只需要去掉首尾的中括号,然后用逗号切割即可。

  1. // Encodes a tree to a single string.
  2. public String serialize(TreeNode root) {
  3. if (root == null) {
  4. return "";
  5. }
  6. List<Integer> preOrder = preorderTraversal(root);
  7. List<Integer> inOrder = inorderTraversal(root);
  8. //两个结果用 "@" 分割
  9. return preOrder + "@" + inOrder;
  10. }
  11. // Decodes your encoded data to tree.
  12. public TreeNode deserialize(String data) {
  13. if (data.length() == 0) {
  14. return null;
  15. }
  16. String[] split = data.split("@");
  17. //还原先序遍历的结果
  18. String[] preStr = split[0].substring(1, split[0].length() - 1).split(",");
  19. int[] preorder = new int[preStr.length];
  20. for (int i = 0; i < preStr.length; i++) {
  21. //trim 是为了去除首尾多余的空格
  22. preorder[i] = Integer.parseInt(preStr[i].trim());
  23. }
  24. //还原中序遍历的结果
  25. String[] inStr = split[1].substring(1, split[1].length() - 1).split(",");
  26. int[] inorder = new int[inStr.length];
  27. for (int i = 0; i < inStr.length; i++) {
  28. inorder[i] = Integer.parseInt(inStr[i].trim());
  29. }
  30. return buildTree(preorder, inorder);
  31. }
  32. // 前序遍历
  33. public List<Integer> preorderTraversal(TreeNode root) {
  34. List<Integer> list = new ArrayList<>();
  35. preorderTraversalHelper(root, list);
  36. return list;
  37. }
  38. private void preorderTraversalHelper(TreeNode root, List<Integer> list) {
  39. if (root == null) {
  40. return;
  41. }
  42. list.add(root.val);
  43. preorderTraversalHelper(root.left, list);
  44. preorderTraversalHelper(root.right, list);
  45. }
  46. // 中序遍历
  47. public List<Integer> inorderTraversal(TreeNode root) {
  48. List<Integer> ans = new ArrayList<>();
  49. getAns(root, ans);
  50. return ans;
  51. }
  52. private void getAns(TreeNode node, List<Integer> ans) {
  53. if (node == null) {
  54. return;
  55. }
  56. getAns(node.left, ans);
  57. ans.add(node.val);
  58. getAns(node.right, ans);
  59. }
  60. //还原二叉树
  61. private TreeNode buildTree(int[] preorder, int[] inorder) {
  62. return buildTreeHelper(preorder, 0, preorder.length, inorder, 0, inorder.length);
  63. }
  64. private TreeNode buildTreeHelper(int[] preorder, int p_start, int p_end, int[] inorder, int i_start, int i_end) {
  65. // preorder 为空,直接返回 null
  66. if (p_start == p_end) {
  67. return null;
  68. }
  69. int root_val = preorder[p_start];
  70. TreeNode root = new TreeNode(root_val);
  71. // 在中序遍历中找到根节点的位置
  72. int i_root_index = 0;
  73. for (int i = i_start; i < i_end; i++) {
  74. if (root_val == inorder[i]) {
  75. i_root_index = i;
  76. break;
  77. }
  78. }
  79. int leftNum = i_root_index - i_start;
  80. // 递归的构造左子树
  81. root.left = buildTreeHelper(preorder, p_start + 1, p_start + leftNum + 1, inorder, i_start, i_root_index);
  82. // 递归的构造右子树
  83. root.right = buildTreeHelper(preorder, p_start + leftNum + 1, p_end, inorder, i_root_index + 1, i_end);
  84. return root;
  85. }

但是竟然遇到了 WA

297. Serialize and Deserialize Binary Tree - 图1

我开始看到的结果的时候真的小小的震惊了一下,哪里出了问题。用的都是之前的代码,只可能是字符串转换那里出问题了。然后调试了一下发现没有问题,甚至又回到之前的题重新提交了一下,也是没有问题的。

先序遍历和中序遍历唯一确定一个二叉树,这个定理错了???然后用上边的样例调试了一下,恍然大悟,这个定理的前提必须得是没有重复的元素。

解法二

好吧,看来不能偷懒。那我们就用 leetcode 所使用的方式吧,通过层次遍历来序列化和还原二叉树。

我们只需要将每一层的序列存到数组中,如果是 null 就存 null。可以结合 102 题 二叉树的层次遍历。

  1. // Encodes a tree to a single string.
  2. public String serialize(TreeNode root) {
  3. if (root == null) {
  4. return "";
  5. }
  6. Queue<TreeNode> queue = new LinkedList<TreeNode>();
  7. List<Integer> res = new LinkedList<Integer>();
  8. queue.offer(root);
  9. //BFS
  10. while (!queue.isEmpty()) {
  11. TreeNode curNode = queue.poll();
  12. if (curNode != null) {
  13. res.add(curNode.val);
  14. queue.offer(curNode.left);
  15. queue.offer(curNode.right);
  16. } else {
  17. res.add(null);
  18. }
  19. }
  20. return res.toString();
  21. }
  22. // Decodes your encoded data to tree.
  23. public TreeNode deserialize(String data) {
  24. if (data.length() == 0) {
  25. return null;
  26. }
  27. //将字符串还原为数组
  28. String[] preStr = data.substring(1, data.length() - 1).split(",");
  29. Integer[] bfsOrder = new Integer[preStr.length];
  30. for (int i = 0; i < preStr.length; i++) {
  31. if (preStr[i].trim().equals("null")) {
  32. bfsOrder[i] = null;
  33. } else {
  34. bfsOrder[i] = Integer.parseInt(preStr[i].trim());
  35. }
  36. }
  37. Queue<TreeNode> queue = new LinkedList<TreeNode>();
  38. TreeNode root = new TreeNode(bfsOrder[0]);
  39. int cur = 1;//通过 cur 指针依次给节点赋值
  40. queue.offer(root);
  41. while (!queue.isEmpty()) {
  42. TreeNode curNode = queue.poll();
  43. if (bfsOrder[cur] != null) {
  44. curNode.left = new TreeNode(bfsOrder[cur]);
  45. queue.add(curNode.left);
  46. }
  47. cur++;
  48. if (bfsOrder[cur] != null) {
  49. curNode.right = new TreeNode(bfsOrder[cur]);
  50. queue.add(curNode.right);
  51. }
  52. cur++;
  53. }
  54. return root;
  55. }

上边的方法已经可以 AC 了,但还可以做一个小小的优化。

如果通过上边的代码,对于下边的二叉树。

  1. 1
  2. / \
  3. 2 3
  4. /
  5. 4

序列化成字符串就是 "[1, 2, 3, 4, null, null, null, null, null]"。就是下边的样子。

  1. n 表示 null
  2. 1
  3. / \
  4. 2 3
  5. / \ / \
  6. 4 n n n
  7. / \
  8. n n

当我们一层一层的还原的时候,因为 TreeNode 的默认值就是 null。所以还原到 4 的时候后边其实就不需要管了。

因为末尾的 null 是没有必要的,所以在返回之前,我们可以把末尾的 null 去掉。此外,deserialize() 函数中,因为我们去掉了末尾的 null,所以当 cur 到达数组末尾的时候要提前结束循环。

  1. // Encodes a tree to a single string.
  2. public String serialize(TreeNode root) {
  3. if (root == null) {
  4. return "";
  5. }
  6. Queue<TreeNode> queue = new LinkedList<TreeNode>();
  7. List<Integer> res = new LinkedList<Integer>();
  8. queue.offer(root);
  9. while (!queue.isEmpty()) {
  10. TreeNode curNode = queue.poll();
  11. if (curNode != null) {
  12. res.add(curNode.val);
  13. queue.offer(curNode.left);
  14. queue.offer(curNode.right);
  15. } else {
  16. res.add(null);
  17. }
  18. }
  19. //去掉末尾的 null
  20. while (true) {
  21. if (res.get(res.size() - 1) == null) {
  22. res.remove(res.size() - 1);
  23. } else {
  24. break;
  25. }
  26. }
  27. return res.toString();
  28. }
  29. // Decodes your encoded data to tree.
  30. public TreeNode deserialize(String data) {
  31. if (data.length() == 0) {
  32. return null;
  33. }
  34. String[] preStr = data.substring(1, data.length() - 1).split(",");
  35. Integer[] bfsOrder = new Integer[preStr.length];
  36. for (int i = 0; i < preStr.length; i++) {
  37. if (preStr[i].trim().equals("null")) {
  38. bfsOrder[i] = null;
  39. } else {
  40. bfsOrder[i] = Integer.parseInt(preStr[i].trim());
  41. }
  42. }
  43. Queue<TreeNode> queue = new LinkedList<TreeNode>();
  44. TreeNode root = new TreeNode(bfsOrder[0]);
  45. int cur = 1;
  46. queue.offer(root);
  47. while (!queue.isEmpty()) {
  48. if (cur == bfsOrder.length) {
  49. break;
  50. }
  51. TreeNode curNode = queue.poll();
  52. if (bfsOrder[cur] != null) {
  53. curNode.left = new TreeNode(bfsOrder[cur]);
  54. queue.add(curNode.left);
  55. }
  56. cur++;
  57. if (cur == bfsOrder.length) {
  58. break;
  59. }
  60. if (bfsOrder[cur] != null) {
  61. curNode.right = new TreeNode(bfsOrder[cur]);
  62. queue.add(curNode.right);
  63. }
  64. cur++;
  65. }
  66. return root;
  67. }

解法三

我们可以只用先序遍历。什么???只用先序遍历,是的,你没有听错。我开始也没往这方面想。直到看到 这里 的题解。

为什么可以只用先序遍历?因为我们先序遍历过程中把遇到的 null 也保存起来了。所以本质上和解法二的 BFS 是一样的。

此外,他没有套用之前先序遍历的代码,重写了先序遍历,在遍历过程中生成序列化的字符串。

  1. private static final String spliter = ",";
  2. private static final String NN = "X"; //当做 null
  3. // Encodes a tree to a single string.
  4. public String serialize(TreeNode root) {
  5. StringBuilder sb = new StringBuilder();
  6. buildString(root, sb);
  7. return sb.toString();
  8. }
  9. private void buildString(TreeNode node, StringBuilder sb) {
  10. if (node == null) {
  11. sb.append(NN).append(spliter);
  12. } else {
  13. sb.append(node.val).append(spliter);
  14. buildString(node.left, sb);
  15. buildString(node.right,sb);
  16. }
  17. }
  18. // Decodes your encoded data to tree.
  19. public TreeNode deserialize(String data) {
  20. Deque<String> nodes = new LinkedList<>();
  21. nodes.addAll(Arrays.asList(data.split(spliter)));
  22. return buildTree(nodes);
  23. }
  24. private TreeNode buildTree(Deque<String> nodes) {
  25. String val = nodes.remove();
  26. if (val.equals(NN)) return null;
  27. else {
  28. TreeNode node = new TreeNode(Integer.valueOf(val));
  29. node.left = buildTree(nodes);
  30. node.right = buildTree(nodes);
  31. return node;
  32. }
  33. }

这道题的话完善了自己脑子里的一些认识,先序遍历和中序遍历可以唯一的确定一个二叉树,前提是元素必须不一样。其实看通过先序遍历和中序遍历还原二叉树的代码也可以知道,因为我们需要找根节点的下标,如果有重复的值,肯定就不行了。

其次,如果二叉树的遍历考虑了 null,那么不管什么遍历我们都能把二叉树还原。

windliang wechat

添加好友一起进步~

如果觉得有帮助的话,可以点击 这里 给一个 star 哦 ^^

如果想系统的学习数据结构和算法,强烈推荐一个我之前学过的课程,可以点击 这里 查看详情