树 JZ17、18、58、23、24、26、57、61、面试题7、32 - I、Ⅱ、Ⅲ、55- I、Ⅱ、68- I、Ⅱ

注:
题号JZ×× :代表牛客网上的题号
面试题×× :代表牛客上没有,《剑指offer》上的题号

文章目录

    • 面试题 7. 重建二叉树
    • JZ17 树的子结构
    • JZ18 二叉树的镜像
    • JZ58 对称的二叉树
    • 面试题32 - I 从上往下打印二叉树(层序遍历)
    • 面试题32-Ⅱ 分行从上到下打印二叉树
    • 面试题32-Ⅲ 之字形打印二叉树
    • JZ23 二叉搜索树的后序遍历序列
    • JZ24 二叉树中和为某一值的路径
    • JZ26 二叉搜索树与双向链表(该题采用LeetCode上的形式)
    • JZ57 二叉树的下一个结点
    • JZ61 序列化二叉树
    • 面试题55 - I. 二叉树的深度
    • 面试题55 - II. 平衡二叉树
    • [面试题 68 - I. 二叉搜索树的最近公共祖先](https://blog.csdn.net/qq_42647047/article/details/113380101)
    • 面试题 68 - II. 二叉树的最近公共祖先
    • 知识点:

面试题 7. 重建二叉树

递归思路

代码
Java:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        TreeNode head = reBuild(preorder , 0 , preorder.length -1 , inorder , 0 , inorder.length -1);
        return head;

    }
    TreeNode reBuild(int[] preorder, int preLeft , int preRight , int[] inorder , int inLeft , int inRight){
        if(preLeft > preRight || inLeft > inRight) return null;
        TreeNode root = new TreeNode(preorder[preLeft]);
        for(int i = inLeft ; i <= inRight ; i++){
            if(inorder[i] == root.val){
                root.left = reBuild(preorder , preLeft +1 , preLeft + (i - inLeft) , inorder , inLeft , i-1);
                root.right = reBuild(preorder , preLeft +(i - inLeft + 1) , preRight , inorder , i + 1 , inRight);
            }
        }
        return root;
    }
}

JZ17 树的子结构

题目描述
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

示例1
输入
{8,8,#,9,#,2,#,5},{8,9,#,2}
返回值
true

题解

第一步,在树A中找到和树B的根节点的值一样的节点R;
这实际上是对树的遍历

树的遍历有递归和循环两种方法,下面是递归方法(以前序遍历为例)

void preOrderTraverse(TreeNode *T)
{
	//不管前中后序,下面两句都在最前面最后递归结束条件
	1if(T==NULL)  return;
	//这里代表对根结点的各种操作,不只是用于输出;
	//另外若是前(中、后)序遍历,这个操作在在两个递归前(中、后);
	2、
	cout<<T->val;##处理根节点;
	
	3、
	preOrderTraverse(T->lchild);
	preOrderTraverse(T->rchild);
}

第二步,判断树A中以R为根节点的子树是不是包含和树B一样的结构;
同样用递归来进行操作

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
    {
        bool result=false;
        //第一步
        if(pRoot1&&pRoot2){
            //===================================================
            if(pRoot1->val==pRoot2->val){
                result=DoseTree1HasTree2(pRoot1,pRoot2);//对应前序遍历里的操作
            }
            //====================================================
            //两个递归,用来遍历树
            if(!result){
                result=HasSubtree(pRoot1->left, pRoot2);
            }
            if(!result){
                result=HasSubtree(pRoot1->right, pRoot2);
            }
        }
        return result;
    }
    
    //第二步
    bool DoseTree1HasTree2(TreeNode* pRoot1, TreeNode* pRoot2){
        //需要先判断pRoot2是否为空,因为若pRoot2在pRoot1的最下面,即两者遍历到最后同时为nullptr,显然结果是true,
        //但若把if(pRoot1==nullptr) return false;放到前面,则会返回false,导致出错。
        if(pRoot2==nullptr) return true;
        if(pRoot1==nullptr) return false;
        if(pRoot1->val!=pRoot2->val) return false;
        bool result=false;
        result=DoseTree1HasTree2(pRoot1->left, pRoot2->left)&&DoseTree1HasTree2(pRoot1->right, pRoot2->right);
        return result;
    }
};

JZ18 二叉树的镜像

题目描述
操作给定的二叉树,将其变换为源二叉树的镜像。

输入描述:
二叉树的镜像定义:源二叉树 
    	    8
    	   /  \
    	  6   10
    	 / \  / \
    	5  7 9 11
    	镜像二叉树
    	    8
    	   /  \
    	  10   6
    	 / \  / \
    	11 9 7  5

画如下图分析
树 JZ17、18、58、23、24、26、57、61、面试题7、32 - I、Ⅱ、Ⅲ、55- I、Ⅱ、68- I、Ⅱ_第1张图片
树 JZ17、18、58、23、24、26、57、61、面试题7、32 - I、Ⅱ、Ⅲ、55- I、Ⅱ、68- I、Ⅱ_第2张图片
代码:

class Solution {
public:
    void Mirror(TreeNode *pRoot) {
        if(!pRoot) return;
        
        if(!pRoot->left&&!pRoot->right) return;
        TreeNode *temp=pRoot->left;
        pRoot->left=pRoot->right;
        pRoot->right=temp;
        //走到这只是左右子节点为空不成立,其反面有1、都为空;2、左为空,右不空;3、右为空,左不空;所以进行递归之前要进行判断。
        if(pRoot->left)
            Mirror(pRoot->left);
        if(pRoot->right)
            Mirror(pRoot->right);
    }
    

};

JZ58 对称的二叉树

题目描述
请实现一个函数,用来判断一棵二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
示例
树 JZ17、18、58、23、24、26、57、61、面试题7、32 - I、Ⅱ、Ⅲ、55- I、Ⅱ、68- I、Ⅱ_第3张图片

左图
输入
{8,6,6,5,7,7,5}
返回值
true
中间图
输入
{8,6,9,5,7,7,5}
返回值
false

方法一:遍历递归

题解:
针对“前中后”序3种遍历方法,我们可以设计一种对应的对称遍历算法,然后两种遍历后结果比较,若相等则对称,否则反之。【3种遍历方法除根节点外,都是按左右子节点的顺序遍历,对应的对称遍历则改为右左子节点遍历】

以前序遍历为例,在这里插入图片描述
递归三部曲
1、递归函数功能(原问题):设置一个递归函数isSame(r1, r2),表示如果对称,返回true,否则返回false
2、递归终止条件r1==nullptr && r2==nulllptr, 直接返回true【一个结点一个结点的对比,若相等就对比下一个,若r1、r2同时为nullptr,则表明从头到尾都相等,则返回true】;
否则,如果只有一个为nullptr,返回false【只有一个返回nullptr说明有一个提前结束遍历,两个树同时开始遍历,有一个提前结束,两个树当然不相等,返回false】
3、下一步递归(子问题):如果根节点(包括子树的根节点)相等,即r1->val == r2->val, 则接着判断两种遍历的其子节点是否相等,即isSame(r1->left, r2->right) && isSame(r1->right, r2->left);

代码:

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
    bool isSymmetrical(TreeNode* pRoot)
    {
        //递归函数功能(原问题)
        return isSame(pRoot,pRoot);
    
    }
    bool isSame(TreeNode* pRoot1, TreeNode* pRoot2){
        //终止条件
        if(pRoot1==nullptr&&pRoot2==nullptr) return true;
        if(pRoot1==nullptr||pRoot2==nullptr) return false;
        //下一步递归(子问题)
        //前序遍历模板之操作部分
        if(pRoot1->val==pRoot2->val)
        	//前序遍历的模板之两个递归部分;这里一个递归同时进行前序遍历和对称前序遍历;
            return isSame(pRoot1->left, pRoot2->right)&&isSame(pRoot1->right, pRoot2->left);
        else
            return false;
    }

};




若最后两个递归部分不用&&,写成标准树的遍历形式为:
        //下一步递归(子问题)
        bool result1=false;
        bool result2=false;
        if(pRoot1->val==pRoot2->val){
            result1=isSame(pRoot1->left, pRoot2->right);
            result2=isSame(pRoot1->right, pRoot2->left);
            return result1&&result2;
        }
            //return isSame(pRoot1->left, pRoot2->right)&&isSame(pRoot1->right, pRoot2->left);
        else
            return false;

方法二,直接对称比较各对称的节点
参考

Java

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public boolean isSymmetric(TreeNode root) {
        boolean result = false;
        if(root == null) return true;
        result = recur(root.left , root.right);
        return result;

    }

    boolean recur(TreeNode L , TreeNode R){
        if(L ==null && R == null) return true;
        if(L == null || R == null || L.val != R.val)  return false;
        boolean result = recur(L.left , R.right) && recur(L.right , R.left);
        return result;
    }
}

面试题32 - I 从上往下打印二叉树(层序遍历)

题目描述
从上往下打印出二叉树的每个节点,同层节点从左至右打印。

示例1
树 JZ17、18、58、23、24、26、57、61、面试题7、32 - I、Ⅱ、Ⅲ、55- I、Ⅱ、68- I、Ⅱ_第4张图片
注:题目要求打印出来,但给的函数返回的是vector ,所以不用进行打印操作,把结果存入vector即可。
题解:

题目要求的二叉树的 从上至下 打印(即按层打印),又称为二叉树的 广度优先搜索(BFS)
BFS 通常借助队列 的先入先出特性来实现。【队列】

为什么需要队列?

以上面示例为例,
树 JZ17、18、58、23、24、26、57、61、面试题7、32 - I、Ⅱ、Ⅲ、55- I、Ⅱ、68- I、Ⅱ_第5张图片

具体步骤:
总:打印哪个节点的同时,把其子节点存入队列
1、初始化:一个队列deque dequeTreeNode, 将root节点入队列dequeTreeNode;
2、如果队列不空,做如下操作:
3、弹出队列头,保存为node,将node的左右非空孩子加入队列
4、做2,3步骤,直到队列为空

BFS的模板如下:

vector<vector<int>> levelOrder(TreeNode* root) {
	//1、初始化:一个队列queue q, 将root节点入队列
    queue<TreeNode*> q;
    q.push(root);
    //2、如果队列不空(每次判断的队列,其包含二叉树同一层节点),做如下操作:
    while(q.size())
    {
        int size=q.size();
        //...
        for(int i=0;i<size;i++)
        {
            ///3、弹出队列头,保存为node,将node的左右非空孩子加入队列
            TreeNode* rt=q.front();q.pop();
            //...
            if(rt->left) q.push(rt->left);
            if(rt->right) q.push(rt->right);
        }
    }
    //return ...
}

代码:

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    vector<int> PrintFromTopToBottom(TreeNode* root) {
        vector<int> ret;
        if(!root) return ret;
        deque<TreeNode*> dequeTreeNode;
        dequeTreeNode.push_back(root);
        while(dequeTreeNode.size()){
            int size=dequeTreeNode.size();
            for(int i=0;i<size;i++){
                TreeNode* Node=dequeTreeNode.front();
                dequeTreeNode.pop_front();
                ret.push_back(Node->val);
                if(Node->left) dequeTreeNode.push_back(Node->left);
                if(Node->right) dequeTreeNode.push_back(Node->right);
            }
        }
        return ret;

    }
};

时间复杂度 O(N): N为二叉树的节点数量,即 BFS 需循环 N 次。
空间复杂度 O(N) : 最差情况下,即当树为平衡二叉树时,最多有 N/2 个树节点同时在 queue 中,使用 O(N) 大小的额外空间。

面试题32-Ⅱ 分行从上到下打印二叉树

从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
树 JZ17、18、58、23、24、26、57、61、面试题7、32 - I、Ⅱ、Ⅲ、55- I、Ⅱ、68- I、Ⅱ_第6张图片
题解:
运用 面试题32 - I的模板即可。需要注意的是要把每一层放到一起,需要维护一个level进行保存。
代码:

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> ret;
        if(!root) return ret;
        deque<TreeNode*> dequeTreeNode;
        dequeTreeNode.push_back(root);
        while(dequeTreeNode.size()){
            vector<int> level;
            int size=dequeTreeNode.size();
            for(int i=0;i<size;i++){
                TreeNode* Node=dequeTreeNode.front();
                dequeTreeNode.pop_front();
                level.push_back(Node->val);
                if(Node->left) dequeTreeNode.push_back(Node->left);
                if(Node->right) dequeTreeNode.push_back(Node->right);
            }
            ret.push_back(level);
        }
        return ret;


    }
};

时间复杂度 O(N): N为二叉树的节点数量,即 BFS 需循环 N 次。
空间复杂度 O(N) : 最差情况下,即当树为平衡二叉树时,最多有 N/2 个树节点同时在 queue 中,使用 O(N) 大小的额外空间。

面试题32-Ⅲ 之字形打印二叉树

请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
树 JZ17、18、58、23、24、26、57、61、面试题7、32 - I、Ⅱ、Ⅲ、55- I、Ⅱ、68- I、Ⅱ_第7张图片
题解:
面试题32 - I. 从上到下打印二叉树 主要考察 树的按层打印 ;
面试题32 - II. 从上到下打印二叉树 II 额外要求 每一层打印到一行 ;
本题额外要求 打印顺序交替变化(建议按顺序做此三道题)。

利用双端队列的两端都能操作的特性,分奇偶层进行处理;

步骤:
BFS 循环: 循环打印奇 / 偶数层,当 deque 为空时跳出;
-------1打印奇数层: 从左向右打印,先左后右加入下层节点;
-------2若 deque 为空,说明向下无偶数层,则跳出;
-------3打印偶数层: 从右向左打印,先右后左加入下层节点;

  • Java
class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList<>();
        if(root == null) return res;
        Deque<TreeNode> dq = new LinkedList<>();
        dq.addLast(root);
        int leval = 1;
        while(!dq.isEmpty()){
            int size = dq.size();
            List<Integer> path = new ArrayList<>();
            if(leval % 2 == 1){//奇数层,从左向右;
                for(int i = 0 ; i < size ; i++){
                    TreeNode node = dq.removeFirst();
                    path.add(node.val);
                    if(node.left != null) dq.addLast(node.left);
                    if(node.right != null) dq.addLast(node.right);
                }
            }else{//偶数层,从右向左;
                for(int i = 0 ; i < size ; i++){
                    TreeNode node = dq.removeLast();
                    path.add(node.val);
                    if(node.right != null) dq.addFirst(node.right);
                    if(node.left != null) dq.addFirst(node.left);
                }
            }
            res.add(new ArrayList<>(path));
            leval++;
        }
        return res;
    }
}
  • C++
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> ret;
        if(!root) return ret;
        deque<TreeNode*> dequeTreeNode;
        dequeTreeNode.push_back(root);
        while(dequeTreeNode.size()){
            vector<int> levelEven;
            int sizeEven=dequeTreeNode.size();
            for(int i=0;i<sizeEven;i++){
                //奇数层【奇  偶 的英语写反了】
                TreeNode* Node=dequeTreeNode.front();
                dequeTreeNode.pop_front();
                levelEven.push_back(Node->val);
                if(Node->left) dequeTreeNode.push_back(Node->left);
                if(Node->right) dequeTreeNode.push_back(Node->right);
            }
            ret.push_back(levelEven);
            if(dequeTreeNode.empty()) break;
            vector<int> levelOdd;
            int sizeOdd=dequeTreeNode.size();
            for(int i=0;i<sizeOdd;i++){  
                //偶数层
                TreeNode* Node=dequeTreeNode.back();
                dequeTreeNode.pop_back();
                levelOdd.push_back(Node->val);
                if(Node->right) dequeTreeNode.push_front(Node->right);
                if(Node->left) dequeTreeNode.push_front(Node->left);
            }
            ret.push_back(levelOdd);
        }
        return ret;

    }
};

时间复杂度 O(N) : N为二叉树的节点数量,即 BFS 需循环 N 次,占用 O(N) ;双端队列的队首和队尾的添加和删除操作的时间复杂度均为 O(1) 。
空间复杂度 O(N) : 最差情况下,即当树为满二叉树时,最多有 N/2 个树节点 同时 在 deque 中,使用 O(N)大小的额外空间。

JZ23 二叉搜索树的后序遍历序列

题目描述:
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则返回true,否则返回false。假设输入的数组的任意两个数字都互不相同

注(前提知识):
二叉搜索树【Binary Search Tree】(即,二叉排序树【Binary Sort Tree】或二叉查找树【Binary Search Tree】)或者是一颗空树,或者是具有下列性质的二叉树:

(1)若它的左子树不空,则左子树上的所有结点的值均小于它的根结点的值。

(2)若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值。

(3)它的左、右子树叶分别是二叉排序树。

二叉排序树是递归定义的。由定义可得:中叙遍历二叉排序树是可以得到一个结点值递增的序列。

题解:
树 JZ17、18、58、23、24、26、57、61、面试题7、32 - I、Ⅱ、Ⅲ、55- I、Ⅱ、68- I、Ⅱ_第8张图片
,递归 + 利用 BST节点值 左 < 根 < 右 和 树后序遍历 左 -> 右 -> 根顺序的特性。
则递归三部曲为:

1、递归函数功能(原问题):
isBST(sequence, 0, length-1),从根节点判断二叉树是否是BST

2、递归终止条件:
if(start>=end) return true;

3、下一步递归(子问题):
isBST(sequence, start, split-1) && isBST(sequence, split, end-1), 判断各左右子树是否是BST;

代码:

class Solution {
public:
    bool VerifySquenceOfBST(vector<int> sequence) {
        if(sequence.size()<=0) return false;
        int length=sequence.size();
        return isBST(sequence, 0, length-1);//1、递归函数功能(原问题):

    }
    
    bool isBST(vector<int> &sequence, int start, int end){//1、递归函数功能(原问题):
        //2、递归终止条件;
        // 当(子)左子树为空(空树也是BST,所以返回true)时(比如[5,4,3,2,1]),下面第一个递归isBST(sequence, start, split-1)即判断空树是否是BST,此时start>end。
        //start=end,即只有一个节点的二叉树,显然是BST;
        if(start>=end) return true;
        int root=sequence[end];
        // 找到右子树起点
        int split=start;
        for(;split<end;split++){
            if(sequence[split]>root)
                break;
        }
        for(int i=split;i<end;i++){
            if(sequence[i]<root)
                return false;
        }
        //3、下一步递归(子问题):
        return isBST(sequence, start, split-1) && isBST(sequence, split, end-1);
    }
};

JZ24 二叉树中和为某一值的路径

题目描述
输入一颗二叉树的根节点和一个整数,按字典序打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。
树 JZ17、18、58、23、24、26、57、61、面试题7、32 - I、Ⅱ、Ⅲ、55- I、Ⅱ、68- I、Ⅱ_第9张图片

题解:
本问题是典型的二叉树方案搜索问题,使用递归+回溯法解决,其包含 前序遍历 + 路径记录 两部分。
1、使用前序遍历原因:
树 JZ17、18、58、23、24、26、57、61、面试题7、32 - I、Ⅱ、Ⅲ、55- I、Ⅱ、68- I、Ⅱ_第10张图片
前序遍历模板

void preOrder(TreeNode *root) {
 // process root
 
 if (root->left) preOrder(root->left);
 if (root->right) preOrder(root->right);
}

2、使用回溯的原因:
以以下二叉树为例进行图表分析,三角标的地方,代表回溯
树 JZ17、18、58、23、24、26、57、61、面试题7、32 - I、Ⅱ、Ⅲ、55- I、Ⅱ、68- I、Ⅱ_第11张图片
树 JZ17、18、58、23、24、26、57、61、面试题7、32 - I、Ⅱ、Ⅲ、55- I、Ⅱ、68- I、Ⅱ_第12张图片

递归算法三部曲:

1、递归函数功能(原问题):
FindPath(TreeNode* root,int sum),从root节点出发,找和为sum的路径

2、递归终止条件:
当root节点为叶子节点并且sum==root->val, 表示找到了一条符合条件的路径

3、下一步递归(子问题):
如果左子树不空,递归左子树FindPath(root->left, sum - root->val),从root->left节点出发,找和为sum - root->val的路径,如果右子树不空,递归右子树,FindPath(root->right, sum - root->val)

代码:

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    vector<vector<int>> FindPath(TreeNode* root,int expectNumber) {
        vector<vector<int>> ret;
        vector<int> path;
        if(!root) return ret;
        dfs(root, expectNumber, ret, path);
            return ret;

    }
    void dfs(TreeNode* root,int expectNumber,vector<vector<int>>& ret,vector<int>& path){
        path.push_back(root->val);
        if(root->val==expectNumber && !root->left && !root->right)
            ret.push_back(path);
        if(root->left) dfs(root->left,expectNumber-root->val,ret,path);
        if(root->right) dfs(root->right,expectNumber-root->val,ret,path);
        path.pop_back();// 代码走到这里,表明要回溯,代表当前path中的root节点我已经不需要了
    }
};

java

class Solution {
    LinkedList<List<Integer>> res = new LinkedList<>();
    LinkedList<Integer> path = new LinkedList<>(); 
    public List<List<Integer>> pathSum(TreeNode root, int target) {
        if(root == null) return res;
        dfs(root , target);
        return res;
        
        


    }

    void dfs(TreeNode root, int target){
        path.add(root.val);
        if(root.val == target && root.left == null && root.right == null) res.add(new LinkedList(path));
        if(root.left != null) dfs(root.left , target - root.val);
        if(root.right != null) dfs(root.right , target - root.val);
        path.removeLast();
    }
}

时间复杂度:O(n), 树的所有节点需要遍历一次
空间复杂度:O(n), 当树退化到链表时,递归空间为O(n)

JZ26 二叉搜索树与双向链表(该题采用LeetCode上的形式)

  • 在得到双向链表的基础上,再得到循环列表。即,也是一道二叉搜索树转换成双向链表的题。
    题目:
    输入一棵二叉搜索树,将该二叉搜索树转换成一个排序循环双向链表要求不能创建任何新的节点,只能调整树中节点指针的指向。

示例:
树 JZ17、18、58、23、24、26、57、61、面试题7、32 - I、Ⅱ、Ⅲ、55- I、Ⅱ、68- I、Ⅱ_第13张图片
注:
由于题目中,要求不能创建任何新的节点,只能调整树中节点指针的指向。
也就是希望可以就地(in-place)完成转换操作,即,函数返回void类型。
题解:
1、
由“搜索二叉树”的特性,其要转换为“排序循环双向链表”,需使用中序遍历
中序遍历的同时改变节点的指针指向,树节点的左指针需要指向前驱,树中节点的右指针需要指向后继;

2、
双向链表: 构建相邻节点(设前驱节点 pre ,当前节点 cur)关系:pre.right = cur ; cur.left = pre;
循环链表: 设链表头节点 head 和尾节点 tail ,则应构建 head.left = tail和 tail.right = head

总的思路:
首先使用中序遍历访问树的各节点 cur ;
并在访问每个节点时构建 cur 和其前驱节点 pre 的指针关系;
中序遍历完成后,最后构建头节点和尾节点的引用指向即可。

代码:

class Solution {
public:
    Node* pre,*head;//初始化pre=head=NULL
    Node* treeToDoublyList(Node* root) {
        if(root==NULL) return NULL;//1、特例处理: 若节点 root 为空,则直接返回;
        dfs(root);//2、转化为双向链表: 调用 dfs(root);
        //3、构建循环链表: 中序遍历完成后,head指向头节点, pre指向尾节点,因此修改 head 和 pre 的双向指针即可。
        head->left=pre;
        pre->right=head;
        return head;
        
    }
    void dfs(Node* cur){
    	//2.1、终止条件: 当节点 cur为空,代表越过叶节点,直接返回;
        if(cur==NULL) return;
        //2.2递归左子树,即 dfs(cur.left) ;
        dfs(cur->left);
        2.3、构建链表
        //中序遍历 cur 一直往左走,走到了最左边的 4 处,此时 pre 还是 nullptr,并且整个遍历过程中只有这个时候pre 会是 nullptr。这个时候我们让 head = cur,也就找到了链表的头节点。
        if(pre==NULL)
            head=cur;
        else
            pre->right=cur;
        cur->left=pre;
        //保存 cur : 更新 pre = cur ,即节点 cur 是后继节点的 pre ;
        pre=cur;
        dfs(cur->right);
    }
};

时间复杂度 O(N) : N 为二叉树的节点数,中序遍历需要访问所有节点。
空间复杂度 O(N) : 最差情况下,即树退化为链表时,递归深度达到 N,系统使用 O(N)栈空间(递归函数所占)。

JZ57 二叉树的下一个结点

参考

JZ61 序列化二叉树

题目:
请实现两个函数,分别用来序列化和反序列化二叉树

注:
1、二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。 序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过某种符号表示空节点(如null),以 某种符号(如 ,)表示一个结点值的结束(如 value,)。

之所以,序列化时通过某种符号表示空节点,是因为:
通常使用的前序、中序、后序、层序遍历记录的二叉树的信息不完整,即唯一的输出序列可能对应着多种二叉树可能性。题目要求的 序列化 和 反序列化 是 可逆操作 。因此,序列化的字符串应携带完整的二叉树信息(即空节点也应表示出)

2、二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。

题解:
1、
其实题目的意思就是写一个函数将二叉树转成字符串(字符串的形式随你定,可以是题目所给的"[1,2,3,null,null,4,5]",也可以是本文代码中"1 2 3 null null 4 5"的形式)。--------使用前中后层序遍历的一种(本次使用层序遍历)。

2、
然后再写一个函数将字符串成功转回一开始的二叉树,此时最后返回的二叉树必须与题目给的一模一样。

代码:
C++

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */

class Codec {
public:

    // Encodes a tree to a single string.
    string serialize(TreeNode* root) {
        if(!root){
            return ""; // 判空
        }
        ostringstream out;
        queue<TreeNode*> bfs;
        bfs.push(root);
        while(!bfs.empty()){
            // 迭代法
            TreeNode* temp = bfs.front();
            bfs.pop();
            if(temp){
                out<< temp -> val << " ";
                bfs.push(temp -> left);
                bfs.push(temp -> right);
            }
            else{
                out<<"null "; // 注意 null 后面有空格
            }
        }
        return out.str(); // out 用来将树转成字符串,元素之间用空格分隔
    }

    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        if(data.empty()){
            return nullptr; // 判空
        }
        istringstream input(data);//初始化input为data
        string info;
        vector<TreeNode*> res; // res 用来将字符串里每个元素转成 TreeNode* 形式的元素
        while(input >> info){//将input的内容以空格为界限一个一个的输入info;
            if(info == "null"){ // 注意 null 后面没空格(因为空格是用来分隔字符串的,不属于字符串)
                res.push_back(nullptr);
            }
            else{
                res.push_back(new TreeNode(stoi(info)));
            }
        }
        int pos = 1;
        for(int i = 0; pos < res.size(); ++i){
            // 本循环将 res 中的所有元素连起来,变成一棵二叉树
            if(!res[i]){
                continue;
            }
            res[i] -> left = res[pos++]; // pos 此时指向左子树,++后指向右子树
            if(pos < res.size()){
                res[i] -> right = res[pos++]; // pos 此时指向右子树,++后指向下一个节点的左子树
            }
        }
        return res[0];
    }
};
// Your Codec object will be instantiated and called as such:
// Codec codec;
// codec.deserialize(codec.serialize(root));


字符串流 istringstream 和 ostringstream 的用法

前中后层序遍历方法合集参考,解析的很好。

Java

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Codec {

    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        if(root == null) return "[]";
        StringBuilder res = new StringBuilder("[");
        Queue<TreeNode> queue = new LinkedList<>() {{ add(root); }};
        while(!queue.isEmpty()){
            TreeNode node = queue.poll();
            if(node != null){
                res.append(node.val + ",");
                queue.add(node.left);
                queue.add(node.right);
            }
            else{
                res.append(null + ",");
            }
        }
        res.deleteCharAt(res.length() - 1);
        res.append("]");
        return res.toString();

        
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        if(data.equals("[]")) return null;
        String[] vals = data.substring(1, data.length() - 1).split(",");
        TreeNode[] res = new TreeNode[vals.length];
        for(int i = 0 ; i < res.length ; i++){
            if(vals[i].equals("null")){
                res[i] = null;
            }else{
                res[i] = new TreeNode(Integer.parseInt(vals[i]));
            }
        }
        TreeNode root = new TreeNode(Integer.parseInt(vals[0]));
        int pos = 1;
        for(int i = 0 ; pos < res.length ; i++){
            if(vals[i].equals("null")){
                continue;
            }
            res[i].left = res[pos++];
            res[i].right = res[pos++];
        }

        return res[0];
        
    }
}

// Your Codec object will be instantiated and called as such:
// Codec codec = new Codec();
// codec.deserialize(codec.serialize(root));

面试题55 - I. 二叉树的深度

题目:

输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。

树 JZ17、18、58、23、24、26、57、61、面试题7、32 - I、Ⅱ、Ⅲ、55- I、Ⅱ、68- I、Ⅱ_第14张图片
题解:

  • 求树的深度需要遍历树的所有节点

树的遍历方式总体分为两类:深度优先搜索(DFS)、广度优先搜索(BFS);
常见的 DFS : 先序遍历、中序遍历、后序遍历;
常见的BFS : 层序遍历(即按层遍历)。

方法一 后序遍历
思路:根节点的深度 = 左右子树深度的最大值 + 1 ;
即,先求出左右子树深度的最大值,即可求出其根节点的深度-------->显然左、右、根是后序遍历的顺序。

class Solution {
public:
    int maxDepth(TreeNode* root) {
        if(!root) return 0;
        int leftDepth = maxDepth(root->left);
        int rightDepth = maxDepth(root->right);
        return max(leftDepth , rightDepth) + 1;
    }
};

时间复杂度 O(N): N为树的节点数量,计算树的深度需要遍历所有节点。
空间复杂度 O(N) : 最差情况下(当树退化为链表时),递归深度可达到 N

方法二:层序遍历(BFS)

  • 每遍历一层,则计数器 +1 ,直到遍历完成,则可得到树的深度

代码:

class Solution {
public:
    int maxDepth(TreeNode* root) {
        int depth=0;
        if(!root) return 0;
        queue<TreeNode*> q;
        q.push(root);
        while(q.size()){
            ++depth;
            int size=q.size();
            for(int i=0; i<size; i++){
                TreeNode* node=q.front();
                q.pop();
                if(node->left) q.push(node->left);
                if(node->right) q.push(node->right);
            }
        }
        return depth;
    }
};

时间复杂度 O(N) : N 为树的节点数量,计算树的深度需要遍历所有节点。
空间复杂度 O(N) : 最差情况下(当树平衡时),队列 queue 同时存储 N/2个节点。

面试题55 - II. 平衡二叉树

题目:
输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。

题解:

后序遍历 + 剪枝 (从底至顶)

本题是在面试题55 - I,从下向上求每个节点深度的基础上,判断每个节点是否是平衡二叉树

具体做法:从下向上求每个节点深度的同时,判断每个节点左右子树深度差是否超过1:

  • 不超过1记录该节点深度,继续递归计算;
  • 超过1,则该节点为根节点的子树不是平衡二叉树,返回-1(也可是其他负数,因为最小平衡二叉树是空树,深度最小为0,所以用负数区分),并直接向上返回(这相当于某种意义上的剪枝)。因为有一个子树不是平衡二叉树整棵树就不是平衡二叉树。

代码:

class Solution {
public:
    bool isBalanced(TreeNode* root) {
        return postInorderTreaverse(root) !=-1;
    }
    int postInorderTreaverse(TreeNode* root){
        if(!root) return 0;
        int leftDepth = postInorderTreaverse(root->left);
        if(leftDepth==-1) return-1;
        int rightDepth = postInorderTreaverse(root->right);
        if(rightDepth==-1) return-1;
        if(abs(leftDepth - rightDepth) <= 1)
            return max(leftDepth , rightDepth) + 1;
        else return -1;
    }
};

时间复杂度 O(N): N 为树的节点数;最差情况下,需要递归遍历树的所有节点。
空间复杂度 O(N): 最差情况下(树退化为链表时),系统递归需要使用 O(N) 的栈空间。

面试题 68 - I. 二叉搜索树的最近公共祖先

面试题 68 - II. 二叉树的最近公共祖先

题目:
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
树 JZ17、18、58、23、24、26、57、61、面试题7、32 - I、Ⅱ、Ⅲ、55- I、Ⅱ、68- I、Ⅱ_第15张图片

示例 1:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
 

说明:

所有节点的值都是唯一的。 p、q 为不同节点且均存在于给定的二叉树中。

方法:递归法后序遍历二叉树。

  • 先判断 root 是否等于 p 或 q。若等于,直接返回 root;若不等于,开始下面的步骤。
  • 我们后序遍历二叉树,先去 root 的左子树找 p 或 q,再去 root 的右子树找 p 或 q,然后看左边和右边的寻找情况。
  • 那么一共只有四种情况:左边没找到右边也没找到、左边找到了右边也找到了、左边没找到右边找到了、左边找到了右边没找到。
    1、
    左边没找到右边也没找到。说明题目给的二叉树根本不包含 p 或 q,返回 nullptr。
    2、
    左边找到了右边也找到了。说明 p 和 q 分居在 root 的两侧,那么最低公共祖先为 root,返回 root。
    3、
    左边没找到右边找到了。说明 p 和 q 全都在 root 的右侧,加上我们在第一步就知道 root 不等于 p 也不等于 q,所以最低公共祖先在 root 的右子树,返回右子树。
    4、
    左边找到了右边没找到。说明 p 和 q 全都在 root 的左侧,加上我们在第一步就知道 root 不等于 p 也不等于 q,所以最低公共祖先在 root 的左子树,返回左子树。

具体思路(树后序遍历递归返回的过程图解)参考

注:
四种情况代码若按下面写,会报错

if(!left && !right) return NULL;
if(left && right) return root;
if(!left && right) return right;
if(left && !right) return left;
non-void function does not return a value in all control paths [-Werror,-Wreturn-type]

意思是:
函数并不是所有分支都有返回值
原因是:
确实只有这四种情况,但编译器不这样认为,因为只有if没有else编译器认为还存在其他情况,所以这里去掉一个if条件加个else,就能包含所有情况了,编译也就不会出现有的情况没有返回值的情况了。

1、去掉一个if条件

if(!left && !right) return NULL;
if(!left && right) return right;
if(left && !right) return left;
return root;//以上情况都不满足就执行这个语句;

2、加个else

if(!left && !right) return NULL;
else if(!left && right) return right;
else if(left && !right) return left;
else return root;

代码:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(!root || p==root || q==root) return root;
        TreeNode* left = lowestCommonAncestor(root->left, p, q);
        TreeNode* right = lowestCommonAncestor(root->right, p, q);
        if(!left && !right) return NULL;
        else if(!left && right) return right;
        else if(left && !right) return left;
        else return root;
    }
};

时间复杂度 O(N): 其中 N为二叉树节点数;最差情况下,需要递归遍历树的所有节点。
空间复杂度 O(N) : 最差情况下,递归深度达到 N ,系统使用 O(N) 大小的额外空间。

知识点:

1、树的val值若定义为float或double,判断是否相等不能用==判断,因为
树 JZ17、18、58、23、24、26、57、61、面试题7、32 - I、Ⅱ、Ⅲ、55- I、Ⅱ、68- I、Ⅱ_第16张图片
若要判断是否相等,需定义一个函数
树 JZ17、18、58、23、24、26、57、61、面试题7、32 - I、Ⅱ、Ⅲ、55- I、Ⅱ、68- I、Ⅱ_第17张图片
2、
技巧
一般返回bool类型的递归程序,&& 与 || 的灵活使用,可以使代码非常简洁。

3、队列

4、
二叉搜索树【Binary Search Tree】(即,二叉排序树【Binary Sort Tree】或二叉查找树【Binary Search Tree】)或者是一颗空树,或者是具有下列性质的二叉树:

(1)若它的左子树不空,则左子树上的所有结点的值均小于它的根结点的值。

(2)若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值。

(3)它的左、右子树叶分别是二叉排序树。

二叉排序树是递归定义的。由定义可得:中叙遍历二叉排序树是可以得到一个结点值递增的序列。

5、
字符串流 istringstream 和 ostringstream 的用法

你可能感兴趣的:(剑指offer,面试,算法,java)