题目

题目来源:力扣(LeetCode)

从左向右遍历一个数组,通过不断将其中的元素插入树中可以逐步地生成一棵二叉搜索树。给定一个由不同节点组成的二叉搜索树,输出所有可能生成此树的数组。


示例:
给定如下二叉树

  1. 2<br /> / \<br /> 1 3<br />返回:

[
[2,1,3],
[2,3,1]
]

思路分析

本题解参考自原书题解。

本题的难点在于二叉搜索树可以对应多个序列,因此从一些基本情况来枚举看看情况是比较好的思路。

首先从最简单的示例看看:

  1. 2<br /> / \<br /> 1 3<br />返回

[
[2,1,3],
[2,3,1]
]
由于是从数组中从左到右插入元素到二叉搜索树中,因此每个元素在已存在的二叉搜索树中的插入位置都是确定的。

可得结论:

数组的第一个元素,必定对应于二叉搜索树的树根。
一个节点对应的子节点在序列上必定在父节点元素之后,如上图子节点1、3在输出的序列中必定在父节点2之后。
猜想:各个序列的差异主要是子树的插入顺序不同,例如此例子可以对应先插左子树后插右子树,或者是先插右子树再插左子树。
得出基本结论后继续看看复杂一些的例子:

  1. 2<br /> / \<br /> 1 4<br /> /<br /> 3

返回

[
[2,1,4,3],
[2,4,1,3],
[2,4,3,1]
]
从本例来看,各个序列的差异主要是子树的插入顺序不同这一猜想不正确。插入的顺序可能是先插部分左子树节点,再插部分右子树节点。
并且插入的顺序始终不会违反结论2,节点3在序列中的位置必定在父节点4之后,因此可以说明序列中子树节点的相对顺序是可以确定的。

因此我们将子树中的节点看做单个序列,如上例中左边的序列为{2},右边的序列为{4,3},将两个序列按照相对顺序进行混合:

[
[1,4,3],
[4,1,3],
[4,3,1]
]
如果此时再在各个序列中加上父节点生成的前缀2,恰好对应了答案。

因此只需递归地找到每个节点对应生成的序列,再在父节点进行混合,即可得到答案。

自上而下 广度优先搜索

  1. // javascript
  2. let ans;
  3. var BSTSequences = function(root) {
  4. ans = [];
  5. let path = [];
  6. // 如果 root = null 返回 [[]]
  7. if (root === null) {
  8. ans.push(path);
  9. return ans;
  10. }
  11. let queue = [root];
  12. // 开始进行回溯
  13. bfs(queue, path);
  14. return ans;
  15. };
  16. /**
  17. * 回溯法 + 广度优先遍历.
  18. */
  19. var bfs = function(queue, path) {
  20. // 终止条件:queue 为空说明已经遍历完,存放结果,直接返回
  21. if (queue.length === 0) {
  22. ans.push(path.slice()); // ⚠️ 必须用 slice
  23. return;
  24. }
  25. let len = queue.length;
  26. // 对 queue 进行循环,对「将当前 cur 节点从 queue 中取出并将其左右子
  27. // 节点加入 queue,再将 cur.val 加入到 path 末尾」的情况进行回溯
  28. while (len > 0) {
  29. let cur = queue.shift();
  30. path.push(cur.val);
  31. // 将左右子节点加入队列
  32. if (cur.left != null) queue.push(cur.left);
  33. if (cur.right != null) queue.push(cur.right);
  34. bfs(queue, path);
  35. // 恢复 path 和 queue,进行回溯(因为是引用数据类型,递归时作为形参会被改变)
  36. path.pop();
  37. if (cur.left != null) queue.pop();
  38. if (cur.right != null) queue.pop();
  39. queue.push(cur);
  40. len--;
  41. }
  42. }

自下而上 深度优先搜索+编织法

  1. // javascript
  2. var BSTSequences = function(root) {
  3. let result = [];
  4. if (root === null) {
  5. result.push([]);
  6. return result;
  7. }
  8. let prefix = [root.val];
  9. // 对左右子树递归
  10. let leftSeq = BSTSequences(root.left);
  11. let rightSeq = BSTSequences(root.right);
  12. // 从每个链表的左右两端交替计算
  13. for (let left of leftSeq) {
  14. for (let right of rightSeq) {
  15. let weaved = [];
  16. weaveLists(left, right, weaved, prefix);
  17. result = result.concat(weaved); // 注意 ⚠️
  18. }
  19. }
  20. return result;
  21. }
  22. // 以所有可能的方式对链表同时交替计算。该算法从一个链表的头部移除元素,递归,并对另一个链表做相同的操作
  23. var weaveLists = function(first, second, results, prefix) {
  24. // 一个链表已空。将剩余部分加入到(复制后的)prefix 中并存储结果
  25. if (first.length === 0 || second.length === 0) {
  26. let result = prefix.slice();
  27. result = result.concat(first, second);
  28. results.push(result);
  29. return;
  30. }
  31. // 将 first 的头部加入到 prefix 后进行递归。移除头部元素会破坏 first,
  32. // 因此我们需要在后续操作时将元素放回
  33. let headFirst = first.shift();
  34. prefix.push(headFirst);
  35. weaveLists(first, second, results, prefix);
  36. prefix.pop();
  37. first.unshift(headFirst);
  38. // 对 second 做相同操作,破坏链表并恢复
  39. let headSecond = second.shift();
  40. prefix.push(headSecond);
  41. weaveLists(first, second, results, prefix);
  42. prefix.pop();
  43. second.unshift(headSecond);
  44. }

参考: https://blog.csdn.net/weixin_45561634/article/details/119721261 https://leetcode-cn.com/problems/bst-sequences-lcci/solution/ru-he-tong-guo-li-zi-de-chu-jie-by-user5707f/ https://leetcode-cn.com/problems/bst-sequences-lcci/solution/mei-yi-ge-jie-dian-du-bi-xu-pai-zai-ta-d-n679/