[leetcode] Verify Preorder Serialization of a Binary Tree

题目: 给定一个字符串,验证它是否是二叉树先序遍历(根->左->右)的结果。

例如,给定一棵如下图所示的二叉树,其先序遍历的序列化结果是"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);
    }
};


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值