题目描述(简单难度)

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:

Clarification: The above format is the same as . 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 题 的先序遍历, 的中序遍历,105 题 的通过先序遍历和中序遍历还原二叉树。

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

toString 方法,它会把 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. List<Integer> ans = new ArrayList<>();
  48. getAns(root, ans);
  49. return ans;
  50. }
  51. if (node == null) {
  52. return;
  53. }
  54. getAns(node.left, ans);
  55. ans.add(node.val);
  56. getAns(node.right, ans);
  57. }
  58. //还原二叉树
  59. private TreeNode buildTree(int[] preorder, int[] inorder) {
  60. return buildTreeHelper(preorder, 0, preorder.length, inorder, 0, inorder.length);
  61. }
  62. private TreeNode buildTreeHelper(int[] preorder, int p_start, int p_end, int[] inorder, int i_start, int i_end) {
  63. // preorder 为空,直接返回 null
  64. if (p_start == p_end) {
  65. return null;
  66. }
  67. int root_val = preorder[p_start];
  68. TreeNode root = new TreeNode(root_val);
  69. // 在中序遍历中找到根节点的位置
  70. int i_root_index = 0;
  71. for (int i = i_start; i < i_end; i++) {
  72. if (root_val == inorder[i]) {
  73. i_root_index = i;
  74. break;
  75. }
  76. }
  77. int leftNum = i_root_index - i_start;
  78. // 递归的构造左子树
  79. root.left = buildTreeHelper(preorder, p_start + 1, p_start + leftNum + 1, inorder, i_start, i_root_index);
  80. // 递归的构造右子树
  81. root.right = buildTreeHelper(preorder, p_start + leftNum + 1, p_end, inorder, i_root_index + 1, i_end);
  82. return root;
  83. }

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

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

解法二

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

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

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

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

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

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

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

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

解法三

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

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

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

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

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

windliang wechat

添加好友一起进步~

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

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