题目: 给定一个字符串,验证它是否是二叉树先序遍历(根->左->右)的结果。
例如,给定一棵如下图所示的二叉树,其先序遍历的序列化结果是"9,3,4,#,#,1,#,#,2,#,6,#,#
",其中"#"表示空节点。
图(1)
样例:
1. "9,3,4,#,#,1,#,#,2,#,6,#,#" return True
2.
"1,#"
return False
3.
"9,#,#,1"
return False
思路:
法一: 观察先序遍历的结果,当出现叶节点时,就会出现两个"#",因为叶节点没有子节点。此时可以将该叶节点消除,即用一个"#"代替,一层层向上归并消除直至根节点,最终只剩一个"#"。可以用栈来实现该过程。以样例1为例来演示下该"归并"过程:
9 <- (入栈)
9,3 <-
9,3,4 <-
9,3,4,# <-
9,3,4,# # -> 9,3,#
9,3,#,1 <-
9,3,#,1,# <-
9,3,#,1,# # -> 9,3,#,# -> 9,#
9,#,2 <-
9,#,2,# <-
9,#,2,#,6 <-
9,#,2,#,6,# <-
9,#,2,#,6,#
# -> 9,#,2,#,# ->9,#,#
->#
每个元素入栈一次,出栈一次,故时间复杂度为O(n),空间复杂度为O(n).代码如下:
class Solution {
public:
void split(string &preorder, char delim, vector<string>& vec){
int pos = preorder.find_first_of(delim);
if(pos != string::npos){
int pre = 0;
while(pos != string::npos){
string tmp = preorder.substr(pre,pos-pre);
vec.push_back(tmp);
pre = pos + 1;
pos = preorder.find_first_of(delim, pre);
}
if(pre < preorder.size()){
vec.push_back(preorder.substr(pre));
}
}else{
vec.push_back(preorder);
}
}
bool isValidSerialization(string preorder) {
if(preorder == "") return true;
bool flag = true;
vector<string> vec;
split(preorder, ',', vec);
stack<string> st;
st.push(vec[0]);
int k = 1;
while(!st.empty() && k < vec.size()){
if(vec[k][0] == '#'){
string tmp = st.top();
while(tmp[0] == '#' && st.size() >= 2){
st.pop();
string dig = st.top();
//cout << dig << endl;
if(dig[0] == '#'){
return false;
}
st.pop();
if(!st.empty()) tmp = st.top();
}
st.push("#");
}else{
st.push(vec[k]);
}
++ k;
}
//cout << st.size() << ": " << st.top() << endl;
if(st.size() == 1 && st.top() == "#") return true;
return false;
}
};
法二:观察图(1),可以将"#"当作叶节点,则根据二叉树的性质:度数为0的节点(叶节点)数目总是比度数为2的节点数目多1,即如果二叉树的叶节点数目为n0,度数为2的节点数目为n2,则有n0 = n2 + 1。
证明:易知二叉树的节点总数 n = n0 + n1 + n2 (1),设 m为其分支总数,则除了根节点以外,每个节点都有一个分支进入,即n = m + 1(2).又有分支总数 m = n1 + 2*n2 (3),即度数为1的节点贡献1个分支,度数为2的节点贡献2个分支。将公式(1)和(3)带入(2),可得 n0 = n2 + 1.
回到本题,将"#"看作叶节点,数字节点均为度数为2的节点,则"#"的数目等于数字节点的数目加1。考虑先序遍历的特点,先根再左子树再右子树,则只有再遍历完成时,才可看到一棵完整的二叉树。即除了扫描完最后一个节点的任何时刻,都不应满足
n0 = n2 + 1
。 因此有如下代码实现:
class Solution {
public:
void split(string &preorder, char delim, vector<string>& vec){
int pos = preorder.find_first_of(delim);
if(pos != string::npos){
int pre = 0;
while(pos != string::npos){
string tmp = preorder.substr(pre,pos-pre);
vec.push_back(tmp);
pre = pos + 1;
pos = preorder.find_first_of(delim, pre);
}
if(pre < preorder.size()){
vec.push_back(preorder.substr(pre));
}
}else{
vec.push_back(preorder);
}
}
// 二叉树性质:度数为0的节点数目(N0) = 度数为2的节点数目(N2)+ 1
// 先序遍历只有遍历完成时才是一棵完整的二叉树
bool isValidSerialization(string preorder) {
if(preorder == "") return true;
bool flag = true;
vector<string> vec;
split(preorder, ',', vec);
int nulls = 0, vals = 0;
for(int i=0; i< vec.size(); ++i){
if(vec[i][0] == '#'){
++ nulls;
}else{
++ vals;
}
if((nulls == vals + 1) && i < vec.size()-1) return false;
}
return (nulls == vals + 1);
}
};
以上实现依然是时间复杂度O(n),空间复杂度O(n)。观察代码可对空间做进一步优化,即我们不需要保存每一个元素,我们只需要保存当前的叶节点数目和非叶结点数目,故空间复杂度可优化为O(1)。代码如下:
class Solution {
public:
// 二叉树性质:度数为0的节点数目(N0) = 度数为2的节点数目(N2)+ 1
// 先序遍历只有遍历完成时才是一棵完整的二叉树
bool isValidSerialization(string preorder) {
if(preorder == "") return true;
int pos = preorder.find_first_of(',');
int nulls = 0, vals = 0;
if(pos != string::npos){
int pre = 0;
while(pos != string::npos){
string tmp = preorder.substr(pre,pos-pre);
if(tmp[0] == '#'){
++ nulls;
}else{
++ vals;
}
if(nulls == vals + 1) return false;
pre = pos + 1;
pos = preorder.find_first_of(',', pre);
}
if(pre < preorder.size()){
if(preorder[pre] == '#'){
++ nulls;
}else{
++ vals;
}
}
}else{
if(preorder[0] == '#'){
++ nulls;
}else{
++ vals;
}
}
return (nulls == vals + 1);
}
};