二叉树基础OJ归纳总结 ------- C语言

   一、单值二叉树

OJ链接https://leetcode.cn/problems/univalued-binary-tree/icon-default.png?t=N7T8https://leetcode.cn/problems/univalued-binary-tree/

 1.1. | 解题思路 |

对二叉树进行前序遍历。首先判断根结点是否为NULL,若为NULL则直接返回true,若不为NULL则看是否存在左子树,若存在则判断左子树的数值与根结点的数值是否相同,不同则返回false,看完左子树后再看右子树,判断右子树的数值与根结点的数值是否相同,不同则返回false。如果上面都没有返回false,则说明到目前为止是符合要求的。还要递归根结点的左子树和右子树,继续执行以上操作。

1.2. | 题解代码 | 

bool isUnivalTree(struct TreeNode* root) {
	if (root == NULL)
	{
		return true;
	}

	if (root->left && root->left->val != root->val)
	{
		return false;
	}

	if (root->right && root->right->val != root->val)
	{
		return false;
	}

	return isUnivalTree(root->left) && isUnivalTree(root->right);
}

二、相同的树

OJ链接https://leetcode.cn/problems/same-tree/icon-default.png?t=N7T8https://leetcode.cn/problems/same-tree/

2.1. | 解题思路 |

对二叉树进行前序遍历先判断根结点,再判断左子树,再判断右子树。若两个二叉树都为空树,则两树相同,返回true;在判断过程中,两树对应的结点有一个是NULL,则两树不相同,返回false;在判断过程中,两树对应的结点都不为NULL,但对应的结点的val不同,则两树不相同,返回false。判断完根结点,再判断左子树,再判断右子树,如此递归,直到分别遍历完两颗二叉树为止。

2.2. | 题解代码 |

bool isSameTree(struct TreeNode* p, struct TreeNode* q) {
    if (p == NULL && q == NULL)
    {
        return true;
    }

    if (p == NULL || q == NULL)
    {
        return false;
    }

    if (p->val != q->val)
    {
        return false;
    }

    return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}

三、对称二叉树

OJ链接https://leetcode.cn/problems/symmetric-tree/icon-default.png?t=N7T8https://leetcode.cn/problems/symmetric-tree/

3.1. | 解题思路 |

判断根结点的左子树与右子树是否关于根结点轴对称。我们可以将此问题转化为:s将右子树整体翻转(使用递归,交换每个左子树的结点和右子树的结点),再判断左子树和翻转后的右子树是否相同,若相同则该树为对称二叉树,返回true;若不相同则该树不是对称二叉树,返回false。

3.2. | 题解代码 |

void ReverseTree(struct TreeNode* root)
{
    if (root == NULL)
    {
        return;
    }

    struct TreeNode* tmp = root->left;
    root->left = root->right;
    root->right = tmp;

    ReverseTree(root->left);
    ReverseTree(root->right);
}

bool isSameTree(struct TreeNode* p, struct TreeNode* q) {
    if (p == NULL && q == NULL)
    {
        return true;
    }

    if (p == NULL || q == NULL)
    {
        return false;
    }

    if (p->val != q->val)
    {
        return false;
    }

    return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}

bool isSymmetric(struct TreeNode* root) {
    ReverseTree(root->right);

    return isSameTree(root->left, root->right);
}

四、二叉树的前序遍历

OJ链接https://leetcode.cn/problems/binary-tree-preorder-traversal/icon-default.png?t=N7T8https://leetcode.cn/problems/binary-tree-preorder-traversal/

4.1. | 解题思路 |

本题要返回一个数组,所以要动态开辟一块空间,开辟多大的空间我们可以通过计算题目给的二叉树有多少个结点,再用结点数乘一个结点的大小就可以求出要开辟多大的空间了。再通过递归式对该二叉树进行前序遍历,将遍历的根结点的val存放到开辟的空间中。要注意的是,因为使用的是递归式的前序遍历,而将根结点的val存放到数组中需要对数组的下标进行控制(即 i = 0,1,2,3,...),所以在调用前序遍历函数时要传一个控制数组下标的变量且要传该变量的地址,这样在递归过程中才能正确的控制数组的下标。

4.2. | 题解代码 |

int TreeSize(struct TreeNode* root)
{
    if (root == NULL)
    {
        return 0;
    }

    return TreeSize(root->left) + TreeSize(root->right) + 1;
}

void PrevOrder(struct TreeNode* root, int* p, int* pi)
{
    if (root == NULL)
    {
        return;
    }

    p[*pi] = root->val;
    (*pi)++;

    PrevOrder(root->left, p, pi);
    PrevOrder(root->right, p, pi);
}

int* preorderTraversal(struct TreeNode* root, int* returnSize) {
    int* data = (int*)malloc(TreeSize(root) * sizeof(int));

    int i = 0;
    PrevOrder(root, data, &i);

    *returnSize = i;
    return data;
}

五、另一颗树的子树

OJ链接https://leetcode.cn/problems/subtree-of-another-tree/icon-default.png?t=N7T8https://leetcode.cn/problems/subtree-of-another-tree/

5.1. | 解题思路 |

遍历二叉树root的每一个结点,并且判断每个结点当前root的子树与二叉树subRoot是否为相同的二叉树(可套用前面判断相同的树的代码),若当前子树与二叉树subRoot为相同的树,则可以判断subRoot是root的子树,返回true;若当前子树与二叉树subRoot不相同,则无法判断subRoot是否为root的子树,此时要继续递归当前子树的左子树和右子树继续进行上述判断,直到递归遍历到root的子树为NULL时,此时说明前面遍历过的root的子树中不存在与subRoot相同的树,所以当遍历到root的子树为NULL时返回false。值得注意的是,左子树中存在或右子树中存在与subRoot相同的树即可证明subRoot是root的子树,反之,左子树和右子树中都不存在与subRoot相同的树,才能证明subRoot不是root的子树

5.2. | 题解代码 |

bool isSameTree(struct TreeNode* p, struct TreeNode* q) {
    if (p == NULL && q == NULL)
    {
        return true;
    }

    if (p == NULL || q == NULL)
    {
        return false;
    }

    if (p->val != q->val)
    {
        return false;
    }

    return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}

bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot) {
    if (root == NULL)
    {
        return false;
    }

    if (isSameTree(root, subRoot))
    {
        return true;
    }

    return isSubtree(root->left, subRoot) || isSubtree(root->right, subRoot);
}

六、二叉树遍历

OJ链接https://www.nowcoder.com/practice/4b91205483694f449f94c179883c1fef?tpId=60&&tqId=29483&rp=1&ru=/activity/oj&qru=/ta/tsing-kaoyan/question-rankingicon-default.png?t=N7T8https://www.nowcoder.com/practice/4b91205483694f449f94c179883c1fef?tpId=60&&tqId=29483&rp=1&ru=/activity/oj&qru=/ta/tsing-kaoyan/question-ranking

6.1. | 解题思路 |

本题的解题步骤分为三大步。

第一步先根据题目给的字符串递归构建一颗二叉树。通过类前序遍历的方式来构建二叉树,在构建函数中将数组中的数据赋值给根结点(当数组元素为'#'时,不往二叉树中写入,继续顺序访问下一个元素,返回NULL),然后再递归构建左子树再递归构建右子树。要注意,在构建函数中我们要顺序遍历数组中的元素,需要控制数组的下标,所以在调用构造二叉树函数之前我们要传一个控制数组下标的变量且要传它的地址,这样在后续的递归构造中才能正确的访问不同下标的元素。

第二步,对构造完成的二叉树进行递归式的中序遍历,并将遍历结果打印出来

第三步,通过类后序遍历的方式实现一个销毁二叉树的函数,防止出现内存泄露的问题。

6.2. | 题解代码 |

#include<stdio.h>
#include<stdlib.h>

typedef char BTDataType;

typedef struct BTNode
{
    BTDataType data;
    struct BTNode* left;
    struct BTNode* right;
}BTNode;

BTNode* CreateTree(BTDataType* p,int* pi)
{
    if (p[*pi] == '#')
    {
        (*pi)++;
        return NULL;
    }

    BTNode* root = (BTNode*)malloc(sizeof(BTNode));
    if (root == NULL)
    {
        perror("malloc fail");
        return NULL;
    }

    root->data = p[*pi];
    (*pi)++;

    root->left = CreateTree(p, pi);
    root->right = CreateTree(p, pi);

    return root;
}

void BTInOrder(BTNode* root)
{
    if (root == NULL)
    {
        return;
    }

    BTInOrder(root->left);
    printf("%c ", root->data);
    BTInOrder(root->right);
}

void DestroyTree(BTNode* root)
{
    if (root == NULL)
    {
        return;
    }

    DestroyTree(root->left);
    DestroyTree(root->right);
    free(root);
}

int main()
{
    BTDataType str[101] = { 0 };
    scanf("%s", str);

    int i = 0;
    BTNode* root = CreateTree(str, &i);

    BTInOrder(root);

    DestroyTree(root);
    root = NULL;

    return 0;
}

七、根据二叉树创建字符串

OJ链接https://leetcode.cn/problems/construct-string-from-binary-tree/icon-default.png?t=N7T8https://leetcode.cn/problems/construct-string-from-binary-tree/

7.1 | 解题思路 |

本题采用递归调用的方式解决。本题要分左子树和右子树两种情况讨论:1.左子树是否为空都要加上(),若左子树为空不加()则会导致二叉树结构歧义;2.右子树不为空则加(),为空则省略()

7.2 | 题解代码 |

class Solution {
public:
    string tree2str(TreeNode* root) {
        if (root == nullptr)
        {
            return string();
        }

        string str;
        str += to_string(root->val);

        // 1.左子树不管为不为空都要加括号
        if (root->left)  // 左子树不为空
        {
            str += '(';
            str += tree2str(root->left);
            str += ')';
        }
        else if (root->left == nullptr && root->right)  // 左子树为空,右子树不为空
        {
            str += "()";
        }

        // 2.右子树不为空则加括号,为空则省略
        if (root->right)
        {
            str += '(';
            str += tree2str(root->right);
            str += ')';
        }

        return str;
    }
};

八、二叉树的层序遍历Ⅰ

OJ链接https://leetcode.cn/problems/binary-tree-level-order-traversal/icon-default.png?t=N7T8https://leetcode.cn/problems/binary-tree-level-order-traversal/

8.1 | 解题思路 |

本题是二叉树层序遍历的变形,看到层序遍历二叉树就要联想到借助队列来实现。

本题的难点主要在于层序遍历的过程中,不好判断当前这一层有多少个节点(题目并没有说该二叉树为满二叉树),所以难以判断在队列中要pop多少次当前这一层的结点才算pop完。而本题有个很巧妙的思路就是:创建一个变量levelSize来记录当前这一层要pop多少个元素,每从队列中pop一个元素,levelSize相应的要减减,pop的同时要将该元素的左孩子和右孩子(如果左孩子和右孩子存在的话)push入队,当levelSize减到0时,表明这一层的元素已经遍历完了,遍历完这一层元素的同时也将下一层的元素push到了队列中。

如此反复,直到队列为空时,就是层序遍历完整颗二叉树了。

8.2 | 题解代码 |

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> vv;
        
        if (root == nullptr)
        {
            return vv;
        }

        queue<TreeNode*> q;
        q.push(root);
        int levelSize = 1;

        while (!q.empty())
        {
            vector<int> vt;
            while (levelSize--)
            {
                TreeNode* front = q.front();
                vt.push_back(front->val);
                q.pop();

                if (front->left)
                {
                    q.push(front->left);
                }

                if (front->right)
                {
                    q.push(front->right);
                }
            }

            vv.push_back(vt);
            levelSize = q.size();
        }

        return vv;
    }
};

九、二叉树的层序遍历Ⅱ

OJ链接icon-default.png?t=N7T8https://leetcode.cn/problems/binary-tree-level-order-traversal-ii/

9.1 | 解题思路 |

在上一题(二叉树的层序遍历Ⅰ) 的基础上在返回二维数组前,调用reverse()逆置一下二维数组即可。

9.2 | 题解代码 |

class Solution {
public:
    vector<vector<int>> levelOrderBottom(TreeNode* root) {
        vector<vector<int>> vv;
        
        if (root == nullptr)
        {
            return vv;
        }

        queue<TreeNode*> q;
        q.push(root);
        int levelSize = 1;

        while (!q.empty())
        {
            vector<int> vt;
            while (levelSize--)
            {
                TreeNode* front = q.front();
                vt.push_back(front->val);
                q.pop();

                if (front->left)
                {
                    q.push(front->left);
                }

                if (front->right)
                {
                    q.push(front->right);
                }
            }

            vv.push_back(vt);
            levelSize = q.size();
        }
        reverse(vv.begin(), vv.end());

        return vv;
    }
};

十、二叉树的最近公共祖先

OJ链接icon-default.png?t=N7T8https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/

10.1 | 解题思路 |

这一题有两种解法:

方法一:常规分析

找两个结点的最近公共祖先,本质在于判断两结点是否为当前结点的左子树结点和右子树结点。若是,则当前结点就是两结点的最近公共祖先;若不是,则递归其他的结点进行判断,以下分情况进行分析:

① p或q有一个结点为root (root == p || root == q)

        直接返回root

② p和q分别是当前结点的左子树结点和右子树结点(或右子树结点和左子树结点)

        返回当前结点

③ p和q都是当前结点的左子树结点

        递归当前结点的左子树,重复上述判断

④ p和q都是当前结点的右子树结点

        递归当前结点的右子树,重复上述判断

 

方法二:转换为,两链表相交找第一个公共结点问题

链表相交找第一个公共结点的详细解答方法八、相交链表(见该链接中的第八题)

解题的核心思想是一样的。

本题需要借助stack来存放从root到p的路径和root到q的路径,获取路径的函数可以使用递归来实现(具体参考题解代码)。

10.2 | 题解代码 |

方法一:

class Solution {
public:
    bool isInTree(TreeNode* root, TreeNode* key)
    {
        if (root == nullptr)
        {
            return false;
        }

        return root == key || isInTree(root->left, key) || isInTree(root->right, key);
    }

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (root == nullptr)
        {
            return nullptr;
        }

        if (root == p || root == q)
        {
            return root;
        }

        bool pInLeft = isInTree(root->left, p);
        bool pInRight = !pInLeft;
        bool qInLeft = isInTree(root->left, q);
        bool qInRight = !qInLeft;

        if ((pInLeft && qInRight) || (pInRight && qInLeft))
        {
            return root;
        }
        
        if (pInLeft && qInLeft)
        {
            return lowestCommonAncestor(root->left, p, q);
        }
        else
        {
            return lowestCommonAncestor(root->right, p, q);
        }
    }
};

方法二:

class Solution {
public:
    bool getPath(TreeNode* root, TreeNode* key, stack<TreeNode*>& path)
    {
        if (root == nullptr)
        {
            return false;
        }

        path.push(root);
        if (root == key)
        {
            return true;
        }
        
        if (getPath(root->left, key, path))
        {
            return true;
        }

        if (getPath(root->right, key, path))
        {
            return true;
        }

        path.pop();
        return false;
    }

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        stack<TreeNode*> pPath;
        stack<TreeNode*> qPath;
        getPath(root, p, pPath);
        getPath(root, q, qPath);

        // 让q和p的路径长度一致
        while (pPath.size() != qPath.size())
        {
            if (pPath.size() > qPath.size())
            {
                pPath.pop();
            }
            else
            {
                qPath.pop();
            }
        }

        // p和q同时pop结点,找到公共祖先
        while (pPath.top() != qPath.top())
        {
            pPath.pop();
            qPath.pop();
        }

        return pPath.top();
    }
};

十一、二叉搜索树与双向链表

OJ链接icon-default.png?t=N7T8https://www.nowcoder.com/practice/947f6eb80d944a84850b0538bf0ec3a5?tpId=13&&tqId=11179&rp=1&ru=/activity/oj&qru=/ta/coding-interviews/question-ranking

11.1 | 解题思路 |

将二叉搜索树转换成有序链表,首先要想到中序遍历能得到二叉搜索树的有序结果。

本题借助中序遍历的思想来解决问题。在中序遍历的递归函数中定义一个prev用于标记前驱结点,cur用于标记当前结点。在中序遍历过程中让cur->left = prev,中序遍历的递归函数中形参prev的类型是引用,这样才能实现上一个结点的后继与cur连接起来,即prev->right = cur(只有当prev是引用时才能对上一个结点产生影响,具体需要结合代码进行分析)。

将二叉搜索树重新建立链接关系后,再找到链表的头结点(while (head->left) {head = head->left;}),最后返回链表头结点即可。

11.2 | 题解代码 |

class Solution {
public:
    void InOrderConvert(TreeNode*& prev, TreeNode* cur)
    {
        if (cur == nullptr)
        {
            return;
        }
  
        InOrderConvert(prev, cur->left);
  
        cur->left = prev;
        if (prev)
        {
            prev->right = cur;
        }
        prev = cur;
  
        InOrderConvert(prev, cur->right);
    }
  
    TreeNode* Convert(TreeNode* pRootOfTree) {
        if (pRootOfTree == nullptr)
        {
            return pRootOfTree;
        }
          
        TreeNode* prev = nullptr;
  
        // 利用中序遍历进行结点链接
        InOrderConvert(prev, pRootOfTree);
  
        // 找链表的头结点
        TreeNode* head = pRootOfTree;
        while (head->left)
        {
            head = head->left;
        }
  
        return head;
    }
};

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值