刷题笔记-树相关

easy

1. 树的子结构.

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)。
分析:这道题最麻烦的地方在于,空树不是任何树的子结构,但非空树的子树为空不影响该树成为其他树的子结构。因此在写代码时必须分别处理这两种情况。

class Solution {
public:
    bool HasSubtreeHelper(TreeNode* pRoot1, TreeNode* pRoot2)
    {
        if(!pRoot2) return true;//树B的子树为空返回true
        if(!pRoot1) return false;
        if(pRoot1->val != pRoot2->val) return false;
        return HasSubtreeHelper(pRoot1->left, pRoot2->left) &&
            HasSubtreeHelper(pRoot1->right, pRoot2->right);
    }
    
    //寻找树A中与树B根节点相同的节点
    bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
    {
        if(!pRoot2 || !pRoot1) return false;//树B为空返回false
        if(HasSubtreeHelper(pRoot1,pRoot2)) return true;
        //下面两行必须调用HasSubtree函数,而不是Helper函数
        if(HasSubtree(pRoot1->left,pRoot2)) return true;
        if(HasSubtree(pRoot1->right,pRoot2)) return true;
        return false;
    }
};

2. 二叉树的镜像

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

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

分析:递归解题,第一轮递归只需要实现左右子树的镜像,接下来递归实现子树的左右子树的镜像即可。注意根节点为空的情况。

class Solution {
public:
    void Mirror(TreeNode *pRoot) {
        if(!pRoot) return;//重要!!
        if(!pRoot->left && !pRoot->right) return;
        //TreeNode *pTem = pRoot->left;
        //pRoot->left = pRoot->right;
        //pRoot->right = pTem;
        swap(pRoot->left, pRoot->right);//两种交换方法皆可
        Mirror(pRoot->left);
        Mirror(pRoot->right);
    }
};

3. 二叉搜索树的后序遍历序列.

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
  分析:后序遍历序列可以被分为三个部分。它的最后一个元素,也就是最后一个部分,是父节点;第一部分比父节点小,是左子树成员;中间部分比父节点大,是右子树成员。如果不能满足这样的规律,该序列就不是任何二叉搜索树后序遍历序列。同理,先序遍历序列的第一个元素是父节点,然后是左右子树成员。最后递归判断序列的前两部分是否可以构成二叉搜索树。

class Solution {
public:
    bool VerifySquenceOfBST(vector<int> sequence) {
        vector<int> lchild, rchild;
        int size = sequence.size(), i=0, mid;
        
        if(size==0) return false;//空子树不会进入这个函数
        if(size<=2) return true;//提前结束判断
        while(i<size && sequence[i]<sequence[size-1])
            ++i;
        mid = i;//mid指向右子树的第一个元素
        while(i<size && sequence[i]>sequence[size-1])
            ++i;
        //如果mid前元素都小于父节点,mid和mid后元素都大于父节点,i==size-1
        if(i!=size-1) return false;
        
        bool flag = true;
        for(i=0; i<mid; ++i)
            lchild.push_back(sequence[i]);
        for(; i<size-1; ++i)
            rchild.push_back(sequence[i]);
        if(flag && !lchild.empty()) 
        	flag = VerifySquenceOfBST(lchild);
        if(flag && !rchild.empty()) 
        	flag = VerifySquenceOfBST(rchild);
        
        return flag;
    }
};

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

输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。
  分析:DFS解题。为了减少代码行数,dfs 函数最好集中于解决是否应该添加当前节点的值到路径中来,而不是当前节点的子节点。and 貌似终于知道什么叫回溯了。

//把写代码思路按照牛客官方题解改了改,改完发现跟官方题解一毛一样(吐血)
class Solution {
public:
    void FPdfs(TreeNode* root, vector<vector<int>>& res, int eN, vector<int>& curV)
    {
        curV.push_back(root->val);
        if(root->val == eN && (!root->left && !root->right) )
            res.push_back(curV);
        if(root->left) 
        	FPdfs(root->left, res, eN-root->val, curV);
        if(root->right) 
        	FPdfs(root->right, res, eN-root->val, curV);
        curV.pop_back();
        return;
    }
    
    vector<vector<int> > FindPath(TreeNode* root,int expectNumber) {
        vector<vector<int> > res;
        if(!root) return res;
        vector<int> vec;
        FPdfs(root, res, expectNumber, vec);
        return res;
    }
};

5. 二叉搜索树和双向链表.

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
  分析:二叉搜索树是有序的树,且按中序遍历方法可以得到有序的数列。中序遍历可以理解为人生的诸多个岔路口,每一个岔路口恰好有两种选择,每种选择都对应着一种命运。假设我们可以将这一段人生经历无数次,意味着我们可以在每个岔路口选择不同的人生轨迹。我们可以选择一路向左走到人生尽头,也可以在最后吃一颗后悔药回到上一步,向右迈出一步,走遍右侧的人生。当然也可以吃两颗后悔药,回撤两步,再选择右侧的道路。通过先向左走直到尽头,再撤回一步向右走一步的方式,我们可以遍历过完所有可能的人生轨迹。这也是深度优先遍历和回溯算法的结合使用。此题就是用这种方式,遍历整个二叉搜索树的,然后在每一条路上改变指针的指向。
将二叉搜索树改为有序的双向链表,可以按照中序方式遍历,将 left 指针指向前一个数,而 right 指针指向后一个数。这里我们用 pre 指针记录前一个数。

class Solution {
public:
    void convert(TreeNode* cur, TreeNode*& pre)
    {
        if(cur==nullptr) return;
        convert(cur->left, pre);
        cur->left = pre;
        if(pre) pre->right = cur;
        pre = cur;
        convert(cur->right, pre);
    }
    
    TreeNode* Convert(TreeNode* pRootOfTree)
    {
        if(!pRootOfTree) return nullptr;
        TreeNode* pre = nullptr;
        convert(pRootOfTree, pre);
        
        TreeNode* res = pRootOfTree;
        while(res->left)
            res = res->left;
        return res;
    }
};

6. 序列化二叉树

请实现两个函数,分别用来序列化和反序列化二叉树。
二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。
二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。
  分析:本题采用了先序遍历。

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
    void tree_to_char(TreeNode* root, char* str, int& i)
    {
        if(!root)
        {
            str[i++]='#';
            return;
        }
        stringstream ss;
        string s;
        ss<<root->val;
        ss>>s;
        int len=s.length(), j=0;
        while(j<len) str[i++]=s[j++];
        str[i++] = '!';
        tree_to_char(root->left, str, i);
        tree_to_char(root->right, str, i);
    }
    
    char* Serialize(TreeNode *root) {    
        char str[1000];
        int i=0;
        tree_to_char(root, str, i);
        str[i]='\0';
        return str;
    }
    
    TreeNode* char_to_tree(char* str, int& i)
    {
        int num=0;
        if(str[i]=='#')
        {
            ++i;
            return nullptr;
        }
        
        while(str[i]!='!')
        {
            num = num*10 + str[i]-'0';
            ++i;
        }
        ++i;
        TreeNode* head = new TreeNode(num);
        head->left = char_to_tree(str,i);//处理左子树
        head->right = char_to_tree(str,i);//处理右子树
        return head;
    }
    
    TreeNode* Deserialize(char *str) {
        int i=0;
        if(str[i]=='\0') return nullptr;
        return char_to_tree(str, i);
    }
};

7. 二叉搜索树的第k个结点

给定一棵二叉搜索树,请找出其中的第k小的结点。例如(5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。
分析:二叉树的中序遍历。

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
    TreeNode* mid_order(TreeNode* pRoot, int& k)
    {//递归实现。函数一定要有返回值,否则会出现段错误。而且要考虑该分支不存在第k小的结点时返回值的设置,这里返回nullptr。
        TreeNode* res = nullptr;
        if(pRoot->left)
        {
            res = mid_order(pRoot->left, k);
            if(res) return res;
        }
        if(--k==0) return pRoot;
        if(pRoot->right) 
        {
            res = mid_order(pRoot->right, k);
            if(res) return res;
        }
        return res;
    }
     
    TreeNode* KthNode(TreeNode* pRoot, int k)
    {
        if(!pRoot) return nullptr;
        return mid_order(pRoot,k);
    }
};

8. 把二叉树打印成多行

从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
  分析:用队列存放本层的二叉树结点。

/*
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> > Print(TreeNode* pRoot) {
        queue<TreeNode*> q, swapq;
        vector<vector<int>> vec;
        vector<int> tmpvec;
        if(!pRoot) return vec;
        q.push(pRoot);
        while(1){
            while(!q.empty())
            {
                TreeNode* t = q.front();
                q.pop();
                tmpvec.push_back(t->val);
                if(t->left) swapq.push(t->left);
                if(t->right) swapq.push(t->right);
            }
            vec.push_back(tmpvec);
            tmpvec.clear();
            if(!swapq.empty()){
                swapq.swap(q);
            }else break;
        }
        return vec;
    }
     
};

9. 二叉树的下一个结点

给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
  分析:二叉树的中序遍历。

/*
struct TreeLinkNode {
    int val;
    struct TreeLinkNode *left;
    struct TreeLinkNode *right;
    struct TreeLinkNode *next;
    TreeLinkNode(int x) :val(x), left(NULL), right(NULL), next(NULL) {
         
    }
};
*/
class Solution {
public:
    TreeLinkNode* GetNext(TreeLinkNode* pNode)
    {
        TreeLinkNode* restree = pNode->right;
        if(!restree){
        //如果pNode的右子节点为空。设当前节点为curNode。
        //如果当前节点是其父节点的左子节点,那么当前节点的下一个节点就是它的父节点。
        //否则不断回溯并更新curNode,直到它成为其父节点的左子节点。如果当前节点是二叉树的最后一个节点,则递归结束时,父节点是nullptr。
            TreeLinkNode* faNode = pNode->next;
            TreeLinkNode* curNode = pNode;
            while(faNode && faNode->right==curNode){
                curNode = faNode;
                faNode = faNode->next;
            }
            return faNode;
        }
        else{//如果当前节点有右子节点,那么它的下一个节点应为右子树中最左侧的节点。
            while(restree->left)
                restree = restree->left;
            return restree;
        }
    }
};

10. 按之字形顺序打印二叉树

请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
分析:类似于第 8 题,用栈替换队列,注意入栈时是先入栈右子节点还是左子节点。

/*
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> > Print(TreeNode* pRoot) {
        stack<TreeNode*> sta1, sta2;
        vector<int> tmp;
        vector<vector<int> > ret;
        int level=0;
        if(!pRoot) return ret;
        sta1.push(pRoot);
        while(1){
            while(!sta1.empty()){
                TreeNode* ele = sta1.top();
                tmp.push_back(ele->val);
                if(level&1){
                    if(ele->right) sta2.push(ele->right);
                    if(ele->left) sta2.push(ele->left);
                }else{
                    if(ele->left) sta2.push(ele->left);
                    if(ele->right) sta2.push(ele->right);
                }
                sta1.pop();
            }
            ret.push_back(tmp);
            tmp.clear();
            if(!sta2.empty()){
                sta2.swap(sta1);
                ++level;
            }else break;
        }
        return ret;
    }
};

11. 对称的二叉树

请实现一个函数,用来判断一棵二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
分析:层序遍历+递归。

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
	//isSym函数用来判断pleft节点和pright节点的val是否相等。
	//递归判断pleft的子节点是否与pright的子节点对称。
    bool isSym(TreeNode* pleft,TreeNode* pright){
        if(pleft && pright)
        {
            if(pleft->val == pright->val)
                return isSym(pleft->left, pright->right) && isSym(pleft->right, pright->left);
            else return false;
        }else if(!pleft && !pright)
            return true;
        else return false;
    }
    bool isSymmetrical(TreeNode* pRoot)
    {
        if(!pRoot) return true;
        return isSym(pRoot->left,pRoot->right);
    }
 
};

12. 有序链表转换二叉搜索树

题目:https://leetcode-cn.com/problems/convert-sorted-list-to-binary-search-tree/
分析:递归,将长链表转换为短链表解决问题。要求转换为高度平衡的二叉树,所以使用快慢指针,慢指针最后指向的就是当前链表的根节点,且慢指针左侧和右侧节点数量相差最多1个。容易出现错误的是,如何确定递归函数的链表范围。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    TreeNode* sortedListpartToBST(ListNode* head, ListNode* tail)
    {
        if(head==tail) return nullptr;
        ListNode *quick=head, *slow=head;
        while(quick!=tail)
        {
            quick = quick->next;
            if(quick==tail) break;
            quick = quick->next;
            slow = slow->next;
        }
        TreeNode* ret = new TreeNode(slow->val);
        ret->left = sortedListpartToBST(head, slow);
        //一定要使用slow->next,而不是slow
        ret->right = sortedListpartToBST(slow->next, tail);
        return ret;

    }
    TreeNode* sortedListToBST(ListNode* head) {
        return sortedListpartToBST(head, nullptr);
    }
};

13. leetcode111-二叉树的最小深度

题目:https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/
分析:仔细读题会发现,本题中对最小深度的定义是从根节点到最近叶子节点的最短路径上的节点数量,而叶子节点的定义是没有子节点的节点。
例子1:如果输入的二叉树为[1,2,3,4,null,null,5],那么最小深度为3,而不是2,节点2和3都各有一个子节点,所以他们不是叶子节点。
例子2:如果输入的二叉树为[1,2],同理,最小深度为2,而不是1,因为节点1并不是叶子节点。
例子3:如果输入的二叉树为[1]。因为节点1既是根节点,又是叶子节点,在这条路径上只有1个节点,所以深度是1。
本题可以使用递归实现。

/**
 * 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:
    int minDepth(TreeNode* root) {
        if(!root) return 0;
        int ldp = minDepth(root->left);
        int rdp = minDepth(root->right);
        
        if(ldp && !rdp) return ldp+1;
        else if(!ldp && rdp) return rdp+1;

        return min(ldp,rdp)+1;
    }
};

medium

稍难一点的二叉树可能需要灵活使用 递归+后序遍历/先序遍历,不知道有没有用中序遍历的。

14. leetcode543-二叉树的直径.

题目:https://leetcode-cn.com/problems/diameter-of-binary-tree/
分析:首先明确每次递归要得到的内容是什么。本题希望得到最长的路径,而最长的路径有可能在左子树中产生,也可能在柚子树(想拥有一颗柚子树)中产生,也可能是左子树最深的路径加柚子树最深的路径,再加上父节点。所以每次递归我们希望得到 root 左子树的深度 ldp、柚子树的深度 rdp、左子树内的最长路径 ldm、柚子树内的最长路径 rdm。那么当回溯到上一级递归函数时,我们可以用得到的左右子树深度的最大值+1,即max(ldp,rdp)+1,作为当前 root 的左/右子树的深度;而当前 root 的左/右子树内的最长路径为max(ldp+rdp+1,max(ldm,rdm))。总之就是很绕。但所有编程题目的核心就是明确自己想要什么吧。

//下面展示一个自带婴儿肥的代码
/**
 * 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:

    void inChildTree(TreeNode* root, int& ldepth, int& ldmeter, int& rdepth, int& rdmeter)
    {
        int ldp=0, ldm=0, rdp=0, rdm=0;
        if(root->left) {
            inChildTree(root->left, ldp, ldm, rdp, rdm);
            ldepth = max(ldp, rdp)+1;
            ldmeter = max(ldp+rdp+1,max(ldm,rdm));
        }
        ldp=0; ldm=0; rdp=0; rdm=0;
        if(root->right) {
            inChildTree(root->right, ldp, ldm, rdp, rdm);
            rdepth = max(ldp, rdp)+1;
            rdmeter = max(ldp+rdp+1,max(ldm,rdm));
        }
    }

    int diameterOfBinaryTree(TreeNode* root) {
        if(!root) return 0;
        int leftdepth=0, leftdiameter=0, rightdepth=0, rightdiameter=0;
        inChildTree(root, leftdepth, leftdiameter, rightdepth, rightdiameter);
        int res = max(leftdepth+rightdepth+1,max(leftdiameter, rightdiameter));
        return res-1;
    }
};

15. leetcode1026-节点与其祖先之间的最大差值.

题目:https://leetcode-cn.com/problems/maximum-difference-between-node-and-ancestor/
分析:最开始的想法是用后序遍历。每次遍历求出以当前节点为根节点的左右子树的最小和最大值,取左右子树整体的最小值和最大值,与根节点的值比较,看是否要更新最大差值。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
 //方法1:后序遍历
class Solution {
public:
    int maxdiff=0;
    void getTreeMinMax(TreeNode* root, int& minValue, int& maxValue)
    {
        if(!root->left && !root->right) return;
        
        
        int lminV=INT_MAX, lmaxV=INT_MIN;
        int rminV=INT_MAX, rmaxV=INT_MIN;
        if(root->left) 
        {
            getTreeMinMax(root->left, lminV,lmaxV);
            lminV = min(lminV, root->left->val);
            lmaxV = max(lmaxV, root->left->val);
        }
        if(root->right)
        {
            getTreeMinMax(root->right, rminV,rmaxV);
            rminV = min(rminV, root->right->val);
            rmaxV = max(rmaxV, root->right->val);
        }
        minValue = min(lminV,rminV);
        maxValue = max(lmaxV,rmaxV);
        maxdiff = max(abs(root->val-minValue),maxdiff);
        maxdiff = max(abs(root->val-maxValue),maxdiff);
    }

    int maxAncestorDiff(TreeNode* root) {
        int minValue=INT_MAX, maxValue=INT_MIN;
        if(!root) return 0;
        getTreeMinMax(root,minValue, maxValue);
        return maxdiff;
    }
};
//方法2:先序遍历
class Solution {
public:
    int maxdiff=0;
    void getTreeMinMax(TreeNode* root, int minValue, int maxValue)
    {
        if(!root) return;
        minValue = min(minValue, root->val);
        maxValue = max(maxValue, root->val);
        maxdiff = max(maxdiff, max(minValue-root->val, maxValue-root->val));
        getTreeMinMax(root->left, minValue, maxValue);
        getTreeMinMax(root->right, minValue, maxValue);
    }

    int maxAncestorDiff(TreeNode* root) {
        int minValue=INT_MAX, maxValue=INT_MIN;
        getTreeMinMax(root,minValue, maxValue);
        return maxdiff;
    }
};

16. leetcode236-二叉树的最近公共祖先

题目:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/
分析:这个题竟然算medium。如果给定的两个节点有一个是根节点,则两者的最近公共祖先一定是根节点,否则向子树遍历。函数定义为TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q);,向左子树遍历是lowestCommonAncestor(root-left, p, q);,右子树同理,函数返回先序遍历首先遇到的节点(p或q),如果没有遇到p或q,就返回nullptr。如果两个函数的返回值都不为空,说明两个节点在不同的子树上,那么他们的最近公共节点是根节点。如果只有一个函数的返回值不为空,说明两个节点在一个子树上,那么返回值就是根节点。

/**
 * 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) return nullptr;
        if(root==p || root==q) return root;
        TreeNode* l = lowestCommonAncestor(root->left, p, q);
        TreeNode* r = lowestCommonAncestor(root->right, p, q);
        if(l && r) return root;
        return (l)?l:r;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值