二叉树相关编程面试题

相关题目

写博客记录不同做法,毕竟面试是提要求的不是直接ac就可以的。

如果是面试,先问问面试官结构是哪一种。

236. 二叉树的最近公共祖先

  • 带有parent的三叉链结构->转换成了链表相交

如果要求空间复杂度为O(1)的话,就要模仿链表相交找到两个节点的高度差,然后两者到同一高度都往上走。

  • 搜索二叉树结构

image-20220122192802381

可以发现这样的性质:如果子节点的值一个比根大一个比根小,那么当前根就是最近公共祖先,不然就可以往左走或者往右走,从而变成递归解决子问题。

  • 普通二叉树

思路一:仿照搜索二叉树的情况

从根开始搜索,查找子节点的位置

  1. 都在左树,递归到左树去找
  2. 都在右树,递归到右树去找
  3. 如果一个在左,一个在右,根就是最近公共祖先。

时间复杂度 O ( N 2 ) O(N^2) O(N2)

思路二:不要求空间复杂度可以用unordered_map来造出父亲表。

时间复杂度 O ( N ) O(N) O(N)

class Solution {
public:
    unordered_map<TreeNode*,TreeNode*> fa;
    unordered_map<TreeNode*,bool> map1;
    void FindFa(TreeNode*& root)
    {
        if(root == nullptr ) return;
        if( root -> left )
        {
            fa[root->left] = root;
            FindFa(root->left);
        } 
        if( root -> right )
        {
            fa[root->right] = root;
            FindFa(root ->right);
        }
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if( p == q ) return q;
        TreeNode* _root =root;
        FindFa(root);
        while( p != _root )
        {
            map1[p] = true;
            p = fa[p];
        }
        while( q!= _root )
        {
            if(map1[q]) return q;
            q = fa[q];
        }
        return q;
    }
};

思路三:使用栈跑两次树存下两个节点的路径。然后对得到的两个栈先进行长度对准,然后判断栈顶是否为同一个元素

时间复杂度 O ( N ) O(N) O(N)

class Solution {
public:
    
    bool FindPath(TreeNode* root , stack<TreeNode*>& st ,const int& target)
    {
        if(root == nullptr ) return false;
        st.push(root);
        if( root -> val == target ) return true;
        
        if( FindPath(root -> left ,st ,target ) )
                return true;
        if( FindPath(root -> right ,st ,target) )
                return true;
        st.pop();
        
        return false;
    }

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        stack<TreeNode*> pst,qst;
        FindPath(root, pst , p -> val);
        FindPath(root ,qst , q -> val);
    
        if( pst.size() < qst.size() ) swap(pst,qst); 
        while( pst.size() != qst.size() ) {
                pst.pop();
        }

        while( pst.top()->val != qst.top()->val )
        {
            
            pst.pop(); qst.pop();
        }
        return pst.top(); 
    }
};

144. 二叉树的前序遍历(非递归)

在递归中将二叉树分割成根,左子树,右子树。

而非递归中则分为:

  1. 左路节点
  2. 左路节点上的右子树

执行完1后执行2,2回到子问题执行1。

image-20220123175800486

原来自己的处理不够标准化,很局限

class Solution {
    public:
    vector<int> preorderTraversal(TreeNode* root) {
        if(!root) return vector<int>();
        vector<int> res;
        stack<TreeNode*> st;
        st.push(root);
        while(!st.empty())
        {
            TreeNode* nownode = st.top();st.pop();
            res.push_back(nownode->val);
            if( nownode -> right) st.push(nownode->right);
            if( nownode -> left) st.push(nownode->left);
        }
        return res;
    }
};

比较标准的写法是下面的:

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> v ;
        stack<TreeNode*> st;
        TreeNode* cur = root;
        while( cur || !st.empty() )
        {
            //循环开始,cur指向哪个节点就表示开始处理哪棵树
            //1.访问左路节点,左路节点入栈
            while( cur != nullptr )
            {
                st.push(cur);
                v.push_back( cur -> val);
                cur = cur -> left;
            }
            //2.依次取栈中左路节点的右子树出来访问
            TreeNode* top = st.top();
            st.pop();
            //子问题的形式去访问这些右子树
            cur = top->right;
        }
        return v;
    }
};

94. 二叉树的中序遍历(非递归)

参考前序,当从栈中取出来的时候表示左子树访问完了,可以访问根。

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> v ;
        stack<TreeNode*> st;
        TreeNode* cur = root;
        while( cur || !st.empty() )
        {
            //循环开始,cur指向哪个节点就表示开始处理哪棵树
            //1.左路节点入栈
            while( cur != nullptr )
            {   
                v.push_back( cur -> val);
                cur = cur -> left;
            }
            //2.依次取栈中左路节点,这时表示这个节点的左子树已经访问完了
 			//先访问它,再以子问题的形式去访问它的右子树
            TreeNode* top = st.top();
            res.push_back(top -> val);
            st.pop();
            
            //子问题的形式去访问这些右子树
            cur = top->right;
        }
        return v;
    }
};

145. 二叉树的后序遍历(非递归)

需要通过判断当前根的右边子树是否已经访问完了来确定当前根的值是否打印。

如果每个节点的value都是不同的。

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> st;
        TreeNode* cur =root;
        while(  cur || !st.empty() )
        {
            while(cur != nullptr )
            {
                st.push(cur);
                cur = cur -> left;
            }
            TreeNode* top = st.top();
            if(top->right== nullptr ) {
               res.push_back(top->val); 
               st.pop();     
            }
            else if( !res.empty() && res.back() == top->right->val )
            {
                res.push_back(top->val);
                st.pop();
            }   
            else{
                cur =top -> right;    
              
            } 
            //递归处理子问题
            //st.pop();
            
        }
        return res;
    }
};

防止每个节点的value存在相同

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> st;
        TreeNode* cur =root;
        TreeNode* prev = root;
        while(  cur || !st.empty() )
        {
            while(cur != nullptr )
            {
                //左路节点保存到栈
                st.push(cur);
                cur = cur -> left;
            }
            TreeNode* top = st.top();//取到一个栈顶元素,左路节点访问完了
            //如果它的右为空,或者右子树已经访问完了(上一个访问节点是top的右节点)
            if(top->right== nullptr || prev == top->right) {
               res.push_back(top->val); 
               st.pop();     
               prev = top;
            }
            else{ //递归处理子问题 
                cur =top -> right; 
            }    
        }
        return res;
    }
};

JZ36 二叉搜索树与双向链表

二叉树的线索化思路变形。

核心原则:我们不知道明天会发生什么,但是我们知道昨天发生了什么。

递归函数参数传引用的好处就是相当于全局只有一个变量。

对于前驱的做法,传一个引用参数prev来进行每个节点前驱的链接。

  1. cur的左指针作为前驱指针,指向前一个节点prev
  2. cur不知道它的下一个是谁,但是prev知道它的下一个是cur
/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    void InOrder(TreeNode* cur , TreeNode*& prev)
    {
        if( cur == nullptr ) return;
        
        InOrder(cur->left, prev);
        
        cur -> left = prev;
        if(prev != nullptr ) prev -> right = cur; 
        prev = cur;
        InOrder(cur->right, prev);
    }
    TreeNode* Convert(TreeNode* pRootOfTree) {
        if( !pRootOfTree ) return nullptr;
        TreeNode* prev = nullptr; 
        InOrder(pRootOfTree,prev);
        while(pRootOfTree ->left !=nullptr)
        {
            pRootOfTree = pRootOfTree -> left;
        }
        return pRootOfTree;
    }
};

假如我们要将其变成降序排,如何处理?

是否可以让左右指针和上面的相反呢?不行,这样会影响递归。

最方便的做法就是对升序的双向链表再链表交换val排序一下就行。

105. 从前序与中序遍历序列构造二叉树

构造二叉树一般采用返回值的方式,在二叉搜索树中比较特殊只使用了bool作为返回值,通过引用来传参数。

前序确定每个树的根,中序确定左右子树区间。

方式一:不使用引用方式来找右子树的起点

class Solution {
public:

    TreeNode* Create(vector<int>&preorder,vector<int>& inorder,int l1,int r1,int l2,int r2)
    {
        if( l1 > r1 || l2 > r2 ) return nullptr;
        //本次要创建的节点
        int _rootval = preorder[l1];
        int i = l2;
        for(  ; i < r2 ;i++ )
        {
            if( inorder[i] == _rootval )
            {
                  break;  
            }
        }
        TreeNode* newnode = new TreeNode(_rootval);
        newnode -> left = Create(preorder,inorder , l1+1, r1, l2, i-1);   
        newnode -> right = Create(preorder,inorder , l1+( i - l2) + 1 ,r1 , i+1, r2);
        return newnode;
    }

    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int l1=0; int r1=(int)preorder.size()-1;
        int l2=0; int r2=(int)inorder.size()-1;
        return Create(preorder,inorder,l1,r1,l2,r2);
    }
};

方式二:使用引用,左子树递归完自动获得右子树的起点

class Solution {
public:

    TreeNode* Create(vector<int>&preorder,vector<int>& inorder,int& l1,int r1,int l2,int r2)
    {
        if( l1 > r1 || l2 > r2 ) return nullptr;
        //本次要创建的节点
        int _rootval = preorder[l1];
        TreeNode* newnode = new TreeNode(_rootval);
        ++l1;//引用写法,由递归时候自动去确定右子树的起点
        int i = l2;
        for(  ; i < r2 ;i++ )
        {
            if( inorder[i] == _rootval )
            {
                  break;  
            }
        }   
        newnode -> left = Create(preorder,inorder , l1, r1, l2, i-1);   
        newnode -> right = Create(preorder,inorder , l1 ,r1 , i+1, r2);
        return newnode;
    }

    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int l1=0; int r1=(int)preorder.size()-1;
        int l2=0; int r2=(int)inorder.size()-1;
        return Create(preorder,inorder,l1,r1,l2,r2);
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值