题目描述
你需要采用前序遍历的方式,讲一个二叉树转换成一个由括号和整数组成的字符串。
空节点则用一对空括号"()"
表示。而且你需要省略所有不影响字符串与原始二叉树之间的一对一映射关系的空括号对。
示例1:
输入: 二叉树:[1, 2, 3, 4]
输出: “1(2(4))(3)”
解释: 原本将是“1(2(4)())(3())”,在省略不必要的空括号之后,它将是"1(2(4))(3)"。
示例2:
输入: 二叉树:[1, 2, 3, null, 4]
输出: “1(2()(4))(3)”
解释: 同示例1 相似,除了我们不能省略第一个对括号来中断输入和输出之间的一对一映射关系。
思路分析
递归
当左右节点存在情况不同时,如何阻止字符串排列,左右节点可分为以下五种情况:
- 当前节点为
null
时,return ""
; - 左右节点均无:
return String.valueOf(root.val)
; - 只存在左节点:
return root.val + "(" + solution(root.left) + ")"
; - 只存在右节点:
return root.val + "()(" + solution(root.left) + ")"
; - 左右节点均存在:
return root.val + "(" + solution(root.left) + ")(" + solution(root.right) + ")";
我们可以看到当左节点为null
时,是否将其展示为空括号()
,取决于右节点是否存在,故我们将情况 3,4 合并为一类;其他两类为情况1 左右节点不存在 与 情况2 只存在左节点.
代码有:
代码1 递归解法
if (root == null) {
return "";
}
if(root.right == null){
if(root.left == null)
return String.valueOf(root.val);
else
return root.val
+ "("
+ solutionRecursive(root.left)
+ ")";
}
// root.left != null && root.right != null
return root.val
+ "("
+ solutionRecursive(root.left)
+ ")("
+ solutionRecursive(root.right)
+ ")";
迭代
迭代的解法相较于递归式解法会稍显复杂,但是会更有利于思维训练;且递归在数据体量较大情况下往往因为使用栈空间过大而更具有理论研究价值(实际多数据业务场景下使用较少),故我们这里给出迭代的解法和思路。
这道题虽是带有字符串的标签,但更大程度是考察二叉树的遍历,有一些基础的小伙伴知道其通过稍微改动二叉树的迭代前序遍历解法即可实现本题要求。
传统的迭代式需要借助两个辅助容器:Stack
来存储遍历的非空节点;HashSet
来判断是否访问过此节点。我们这里还需要借助StringBuilder
来保存返回结果,且在遍历时,我们不缺分根节点,当返回结果时我们直接取去掉首位括号的中间字符串,即(1(2()(4))(3)).subString(1, len - 1)
,即为返回结果1(2()(4))(3)
,这里给出核心代码:
Stack<TreeNode> stack = new Stack<>();
HashSet<TreeNode> visitedSet = new HashSet<>();
StringBuilder res = new StringBuiler();
stack.push(root);
while(!stack.empty()){
TreeNode node = stack.peek();
if(visitedSet.contains(node)){
stack.pop();
res.append(")");
}else{
visitedSet.add(node);
res.append("(" + node.val);
if(node.left == null && node.right != null)
res.append("()");
if(node.right != null) // attention here !!!!
stack.push(node.right);
if(node.left!= null)
stack.push(node.left);
}
}
return res.subString(1, res.length() - 1);
这里需要注意的是,访问新节点时,先判断右节点是否存在,因为根据栈先进后出 的数据访问特点,我们需要先判断左节点,之后是右节点,故先将右节点判存后入栈。
解题代码
1. 递归
public static String solutionRecursive(TreeNode root) {
if (root == null) {
return "";
}
if(root.right == null){
if(root.left == null)
return String.valueOf(root.val);
else
return root.val
+ "("
+ solutionRecursive(root.left)
+ ")";
}
// root.left != null && root.right != null
return root.val
+ "("
+ solutionRecursive(root.left)
+ ")("
+ solutionRecursive(root.right)
+ ")";
}
2. 迭代
public static String solutionIterative(TreeNode root){
if(root == null) return "";
StringBuilder res = new StringBuilder();
HashSet<TreeNode> visitedSet = new HashSet<>();
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while(!stack.empty()){
TreeNode node = stack.peek();
if(visitedSet.contains(node)){
res.append(")");
stack.pop();
}else{
visitedSet.add(node);
res.append("(" + node.val);
if(node.left == null && node.right != null)
res.append("()");
if(node.right != null)
stack.push(node.right);
if(node.left != null)
stack.push(node.left);
}
}
return res.substring(1, res.length() - 1);
}
复杂度分析
两种解法的复杂度相同,我们统一分析:
- 递归
时间复杂度: 我们对二叉树进行了一次遍历,故时间复杂度为O(n)
;
空间复杂度: 借助辅助容器 res
,故空间复杂度为O(n)
;
- 迭代
时间复杂度: 时间复杂度为O(n)
;
空间复杂度: 我们这里借助了stack, visited
,以及返回结果res
,最坏情况下为3n
,故空间复杂度为O(n)
;
Github源码
完整可运行文件请访问GitHub。