二叉树进阶 - 下

目录​​​​​​​

1. 二叉树搜索树应用分析​​​​​​​

1.1. K模型 && KV模型

1.2. 二叉搜索树的性能分析

2. 二叉树进阶面试题

2.1. 根据一棵树的前序遍历与中序遍历构造二叉树

2.2. 根据一棵树的中序遍历与后序遍历构造二叉树

2.3. 二叉树的前序遍历  --- 非递归

2.4. 二叉树的中序遍历 --- 非递归

2.5.  二叉树的后序遍历  --- 非递归

2.6. 二叉树的层序遍历

2.7. 二叉树的层序遍历Ⅱ

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

2.8.1.  第二种思路实现

2.8.2.  第三种思路实现

2.9. 二叉搜素树和双向链表

2.10. 二叉树创建字符串


1. 二叉树搜索树应用分析

1.1. K模型 && KV模型

二叉搜索树的应用:

  1. K模型 --- 在不在的问题 K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值,解决的是在不在的问题。​​​​​​​
    • ​​​​​​​​​​​​​​比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树 ,在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。 还有我们日常生活中的门禁系统、车库系统等等。
  2. KV模型 --- 通过一个值查找另一个值,每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见: ​​​​​​​
    • 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;
    • 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。 

KV模型,假如我现在要实现简易版的英汉词典,通过英文单词,查询对应的中文,这时候我们的搜索二叉树的每个节点就是<key,value>的键值对,通过key查找我们的value:

#include <iostream>
#include <string>
namespace Xq
{

	template<class T, class K>
	struct BinarySearchTreeNode
	{
		BinarySearchTreeNode<T, K>* _left;
		BinarySearchTreeNode<T, K>* _right;
		T _key;
		K _value;

		BinarySearchTreeNode(const T& key, const K& value) :_key(key),_value(value), _left(nullptr), _right(nullptr) {}
	};

	template<class T, class K>
	class BinarySearchTree
	{
	private:
		typedef BinarySearchTreeNode<T, K> Node;
	public:
		BinarySearchTree(Node* root = nullptr) :_root(root) {}

		bool insert(const T& key, const K& value)
		{
			// 1. 如果是空树,直接对_root赋值即可,插入成功并返回true
			if (_root == nullptr)
			{
				_root = new Node(key, value);
				return true;
			}
			else
			{
				// step 1: 先找目标位置
				Node* cur = _root;
				// 为了更好的连接新节点, 因此记录父节点
				Node* parent = nullptr;

				while (cur)
				{
					// 如果当前节点的Key大于目标Key
					// 当前节点应该向左子树走
					if (cur->_key > key)
					{
						parent = cur;
						cur = cur->_left;
					}
					// 如果当前节点的Key小于目标Key
					// 当前节点应该向右子树走
					else if (cur->_key < key)
					{
						parent = cur;
						cur = cur->_right;
					}
					else
					{
						// 找到了相同的 key, 在这里不插入
						return false;
					}
				}

				// cur 走到了空, 即 cur 就是合适的位置
				cur = new Node(key, value);
				// 我们需要判断cur是parent的左节点还是右节点
				// 如果key小于parent的key,那么插入左节点
				if (key < parent->_key)
					parent->_left = cur;
				// 反之连接到右节点
				else
					parent->_right = cur;
				return true;
			}
		}

		Node* find(const T& key)
		{
			Node* cur = _root;
			while (cur)
			{
				if (cur->_key > key)
					cur = cur->_left;
				else if (cur->_key < key)
					cur = cur->_right;
				else
					return cur;
			}
			return nullptr;
		}

		bool erase(const T& key)
		{
			// 先找要删除的节点
			Node* del = _root;
			Node* del_parent = nullptr;

			while (del)
			{
				if (del->_key < key)
				{
					del_parent = del;
					del = del->_right;
				}
				else if (del->_key > key)
				{
					del_parent = del;
					del = del->_left;
				}
				else
				{
					// 锁定了要删除的节点
					// 分三种情况:
					// case 1: 左子树为空
					if (del->_left == nullptr)
					{
						// 如果要删除的节点是根
						if (del == _root)
						{
							Node* newroot = del->_right;
							delete _root;
							_root = newroot;
						}
						else
						{
							// 托孤法删除
							if (del_parent->_left == del)
								del_parent->_left = del->_right;
							else
								del_parent->_right = del->_right;
							delete del;
							del = nullptr;
						}
					}
					// case 2: 右子树为空
					else if (del->_right == nullptr)
					{
						if (_root == del)
						{
							Node* newroot = del->_left;
							delete _root;
							_root = newroot;
						}
						else
						{
							if (del_parent->_left == del)
								del_parent->_left = del->_left;
							else
								del_parent->_right = del->_left;
							delete del;
							del = nullptr;
						}
					}
					// case 3: 左右子树都不为空
					else
					{
						// 从被删除节点开始, 找右子树的最小(左)节点 || 找左子树的最大(右)节点
						if (del->_right)
							_erase_right_min_node(del);
						else
							_erase_left_max_node(del);
					}
					return true;
				}
			}

			return false;
		}

		Node* find_recursion(const T& key)
		{
			return _find_recursion(_root, key);
		}

		bool insert_recursion(const T& key, const K& value)
		{
			return _insert_recursion(_root, key, value);
		}

		bool erase_recursion(const T& key)
		{
			return _erase_recursion(_root, key);
		}

		~BinarySearchTree()
		{
			_BSTDestroy(_root);
		}

		BinarySearchTree(const BinarySearchTree<T, K>& copy)
		{
			_root = _creat_new_root(copy._root);
		}

		// 传值传参会进行拷贝构造
		BinarySearchTree<T, K>& operator=(BinarySearchTree<T, K> copy)
		{
			std::swap(_root, copy._root);
			return *this;
		}

		void InOrder()
		{
			_InOrder(_root);
			std::cout << std::endl;
		}

	private:
		void _InOrder(Node* root)
		{
			if (root)
			{
				_InOrder(root->_left);
				std::cout << root->_key << " ";
				_InOrder(root->_right);
			}

		}

		Node* _find_recursion(Node* root, const T& key)
		{
			if (root == nullptr)
				return root;
			else
			{
				if (root->_key < key)
					return _find_recursion(root->_right, key);
				else if (root->_key > key)
					_find_recursion(root->_left, key);
				else
					return root;
			}
		}

		bool _insert_recursion(Node*& root, const T& key, const K& value)
		{
			if (root == nullptr)
			{
				root = new Node(key, value);
				return true;
			}
			else
			{
				if (root->_key < key)
					return _insert_recursion(root->_right, key);
				else if (root->_key > key)
					return _insert_recursion(root->_left, key);
				else
					return false;
			}
		}

		void _erase_right_min_node(Node* del)
		{
			// 从被删除结点开始, 找右子树的最小(左)节点
			Node* right_min = del->_right;
			// 并记录这个节点的父亲节点, 让其从del开始
			Node* right_min_parent = del;
			while (right_min->_left)
			{
				right_min_parent = right_min;
				right_min = right_min->_left;
			}
			// 交换这个节点和要删除节点的 key
			std::swap(del->_key, right_min->_key);

			// 将删除 del 转化为删除 right_min (托孤法删除)
			if (right_min_parent->_left == right_min)
				right_min_parent->_left = right_min->_right;
			else
				right_min_parent->_right = right_min->_right;
			delete right_min;
			right_min = nullptr;
		}

		void _erase_left_max_node(Node* del)
		{
			// 从被删除节点开始, 找左子树的最大(右)节点
			Node* left_max = del->_left;
			// 并记录这个节点的父亲节点, 让其从del开始
			Node* left_max_parent = del;
			while (left_max->_right)
			{
				left_max_parent = left_max;
				left_max = left_max->_right;
			}
			// 交换这个节点和要删除节点的 key
			std::swap(del->_key, left_max->_key);

			// 将删除 del 转化为删除 left_max (托孤法删除)
			if (left_max_parent->_left == left_max)
				left_max_parent->_left = left_max->_left;
			else
				left_max_parent->_right = left_max->_left;
			delete left_max;
			left_max = nullptr;
		}

		bool _erase_recursion(Node*& root, const T& key)
		{
			if (!root)
				return false;
			else
			{
				// 如果当前节点的key > 目标key,那么递归它的左子树即可
				if (root->_key > key)
					return _erase_recursion(root->_left, key);
				// 如果当前节点的key < 目标key,那么递归它的右子树即可
				else if (root->_key < key)
					return _erase_recursion(root->_right, key);
				// 如果找到了,进行删除
				else
				{
					// 此时的root就是要删除的节点
					Node* del = root;
					// a. 左子树为空
					if (root->_left == nullptr)
						root = root->_right;
					// b. 右子树为空
					else if (root->_right == nullptr)
						root = root->_left;
					// c. 左右子树都不为空
					else
					{
						// 左子树的最右节点
						Node* left_max = root->_left;
						while (left_max->_right)
							left_max = left_max->_right;
						// 交换"合适节点"和"被删除节点"的key
						std::swap(left_max->_key, root->_key);
						// 在这里递归左子树即可
						return _erase_recursion(root->_left, key);
					}
					delete del;
					del = nullptr;
					return true;
				}
			}
		}

		Node* _creat_new_root(Node* root)
		{
			// 如果遇到空了,就不用构造了
			if (root == nullptr)
				return nullptr;
			else
			{
				// 根据前序的思想(NLR),依次构造它的根、左子树、右子树   
				// 同时将它们连接起来
				Node* new_root = new Node(root->_key);
				new_root->_left = _creat_new_root(root->_left);
				new_root->_right = _creat_new_root(root->_right);
				return new_root;
			}
		}

		// 注意我们这里传递的是根的引用
		void _BSTDestroy(Node*& root)
		{
			if (root == nullptr)
				return;
			else
			{
				// 依据后序的思想
				_BSTDestroy(root->_left);
				_BSTDestroy(root->_right);
				delete root;
				root = nullptr;
			}
		}

	private:
		Node* _root;
	};
}

void Test1(void)
{
	// 这就是我们的KV模型,通过我们的单词可以查到对应的中文
	Xq::BinarySearchTree<std::string, std::string> dict;
	dict.insert("left", "左边");
	dict.insert("right", "右边");
	dict.insert("superman", "超人");
	dict.insert("step", "步骤");
	std::string str;
	while (std::cin >> str)
	{
		Xq::BinarySearchTreeNode<std::string, std::string>* ret = dict.find(str);
		if (ret == nullptr)
			std::cout << "没有此单词" << std::endl;
		else
			std::cout << ": " << ret->_value << std::endl;
	}
}

int main()
{
	Test1();
	return 0;
}

假如我现在要实现统计动物园中不同动物的个数呢?

其实这也是一个KV模型,只不过这里的V是动物的个数,实现如下:

// 打印 key 和 value
void _InOrder(Node* root)
{
	if (root)
	{
		_InOrder(root->_left);
		std::cout << root->_key << ":" << root->_value << std::endl;
		_InOrder(root->_right);
	}
}

void Test2(void)
{
	Xq::BinarySearchTree<std::string, size_t> animals_size;
	std::string str[6] = { "狮子", "老虎", "猴子", "猩猩", "大熊猫", "黑熊" };
	srand((size_t)time(nullptr));
	int count = 10;
	while (count--)
	{
		std::string arr = str[rand() % 6];

		Xq::BinarySearchTreeNode<std::string, size_t>* ret = animals_size.find(arr);
		if (ret == nullptr)
		{
			animals_size.insert(arr, 1);
		}
		else
		{
			++ret->_value;
		}
	}
	animals_size.InOrder();
}

1.2. 二叉搜索树的性能分析

相信经过我们前面对于BST的模拟实现,我们对于增删查改有了一定的理解。

我们可能会认为它们的时间复杂度就是O(logN),但是呢,这只是较好的情况或者称之为一般情况,什么意思呢?

就是当这棵二叉树是较为均衡的一棵二叉树,那么它的增删查改时间复杂度的确是O(logN),但是如果是下面这棵树呢?

可以看到,这棵树是一棵"歪脖子树",它只有左子树,没有右子树,那么此时对于这棵树而言,他的增删查改就变成了O(N),也就是说,对于普通的搜索二叉树的增删查改的时间复杂度我们认为是O(N)(最坏情况)。

因此,为了避免这种最坏情况,我们需要调整搜索二叉树的结构,让它避免产生这种结构。

2. 二叉树进阶面试题

2.1. 根据一棵树的前序遍历与中序遍历构造二叉树

105. 从前序与中序遍历序列构造二叉树 - 力扣(LeetCode)

思路:前序NLR 中序 LNR,因此前序可以确立根,中序可以分割左右子区间。

Solution:

  • Step 1: 通过前序确定根;
  • Step 2: 通过中序分割左右子区间,由于前序确定的根,那么在中序中根左边的元素在左子树、在根右边的元素在右子树;
  • Step 3: 递归子问题,通过递归左右子树,并通过返回值连接它们

代码如下:

class Solution {
public:
    TreeNode* _build_tree(std::vector<int>& pre_order,std::vector<int> in_order,int& pre_index, int in_begin,int in_end)
    {
        // 注意,如果是非法区间,返回nullptr
        // 参数解释: pre_index是前序的下标,由于我们需要在每个子问题上都是同一个值,因此这里使用引用
        // in_begin 和 in_end 分别是中序区间的开始和结束
        if(in_begin > in_end)
            return nullptr;
        // 因为pre_order.length >= 1,因此没有空表,直接构造根节点,前序的第一个elements就是root
        //1. 前序确定根
        TreeNode* root = new TreeNode(pre_order[pre_index++]);

        //2. 中序分割左右子区间
        //a. 找根
        int in_index = in_begin;
        //b. 分割左右子区间
        //   区间被分为 [in_begin,in_index-1] in_index [in_index+1,in_end]
        while(in_order[in_index] != pre_order[pre_index-1])
            ++in_index;

        //3. 递归子问题
        root->left = _build_tree(pre_order,in_order,pre_index,in_begin,in_index-1);
        root->right = _build_tree(pre_order,in_order,pre_index,in_index+1,in_end);

        //4. 返回根
        return root;
    }

    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int pre_index = 0;
        return _build_tree(preorder,inorder,pre_index,0,inorder.size()-1);
    }
};

2.2. 根据一棵树的中序遍历与后序遍历构造二叉树

106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode)

思路:后续确定根,中序分割左右子树

由于题目明确了没有重复元素。

Solution:

  • Step 1: 通过后续确定根;
  • Step 2: 通过中序分割左右子树;
  • Step 3: 递归子问题,并通过返回值链接它们。

由于我们是后序(LRN)确立根,因此递归子问题时需要先递归右子树、在递归左子树。

class Solution {
public:
    TreeNode* _build_tree(std::vector<int>& in_order,std::vector<int>& post_order,int& post_index,int in_begin, int in_end)
    {
        // 注意: 对于非法的子区间,直接返回nullptr
        if(in_begin > in_end)
            return nullptr;

        //1. 后序确定根,题干明确了不可能为空表,因此直接构造根
        TreeNode* root = new TreeNode(post_order[post_index--]);

        //2. 根据根,分割中序的左右区间
        int in_index = in_begin;
        while(post_order[post_index+1] != in_order[in_index])
            ++in_index;
        // 将左右子树分为 [in_begin,in_index-1] in_index [in_index+1,in_end]

        //3. 递归子问题
        //注意: 因为我们是后序确立根(LRN),因此需要先递归右子树、在递归左子树
        root->right = _build_tree(in_order,post_order,post_index,in_index+1,in_end);
        root->left = _build_tree(in_order,post_order,post_index,in_begin,in_index-1);
        
        //4. 返回根
        return root;
    }

    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        int post_index = postorder.size() - 1;
        return _build_tree(inorder,postorder,post_index,0,inorder.size()-1);
    }
};

2.3. 二叉树的前序遍历  --- 非递归

144. 二叉树的前序遍历 - 力扣(LeetCode)

思路:

前序的非递归解决方法是将一颗树分为:

  1. 左路节点

  2. 左路节点的右子树

步骤如下:

  • step 1: 先将左路节点同时入栈和vector(目的是为了访问左路节点的右子树);
  • step 2: 取栈顶数据,出栈,访问右子树。继续重复step1的过程。

注意:如果树不为空或者栈不为空就继续,树不为空说明树还有数据,栈不为空说明有可能还有右子树没有被访问。

class Solution {
public:
    void _pre_order(TreeNode* root,std::vector<int>& v,std::stack<TreeNode*>& st)
    {
        TreeNode* cur = root;
        // 树还有数据 或者 栈还有数据就继续
        while(cur || !st.empty())
        {
            // 将左路节点分别入栈、vector中,栈中数据的目的是为了获得我们的右子树
            while(cur)
            {
                v.push_back(cur->val);
                st.push(cur);
                cur = cur->left;
            }
            // 去栈顶数据
            TreeNode* top = st.top();
            st.pop();
            // 遍历我们的右子树
            cur = top->right; 
        }
    }
    vector<int> preorderTraversal(TreeNode* root) {
        std::vector<int> v;
        std::stack<TreeNode*> st;
        _pre_order(root,v,st);
        return v;
    }
};

2.4. 二叉树的中序遍历 --- 非递归

思路:根据中序的思想(LNR),我们的迭代写法:

  • Step 1: 先向栈入左路节点;
  • Step 2: 取栈顶数据入vector,pop栈顶数据,如果栈顶数据的右子树不为空,终止循环,向栈入右子树的左路节点(重复Step 1)。

从栈里面取左路节点,意味着这个节点的左子树被访问完了,因此要访问右子树的左路节点。

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        std::vector<int> v;
        if(nullptr == root) return v;
        std::stack<TreeNode*> st;
        TreeNode* cur = root;
        // 树还有数据  || 栈不为空
        while(cur || !st.empty())
        {
            // 先向栈入左路节点
            while(cur)
            {
                st.push(cur);
                cur = cur->left;
            }
            // 取栈顶数据,入vector,如果栈顶数据的右节点不为空,终止循环,继续向栈入右子树的左路节点
            //从栈里面取左路节点,意味着这个节点的左子树被访问完了,因此要访问右子树的左路节点。
            while(!st.empty())
            {
                TreeNode* top= st.top();
                st.pop();
                v.push_back(top->val);
                if(top->right)
                {
                    cur = top->right;
                    break;
                }
            }
        }
        return v;
    }
};

2.5.  二叉树的后序遍历  --- 非递归

思路:通过栈存储左路节点,用两个vector,第一个vector存储后序遍历的节点(为了判断某个节点的右子树是否被访问过),第二个vector存储后序遍历的节点的val(用于返回结果);

如何判断某个节点的右子树被访问过?

只要这个节点的右子树 == 存储后序遍历节点的vector的back(),那么就意味着这个节点的右子树已经被访问过了,此时只需要将这个节点分别push到两个vector中,并pop栈顶数据(就是这个节点),进入下一次循环即可。

只要某个节点的右树为空树,那么就将这个结点分别push到两个vector中;否则,入右树的左路节点。

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        // 存储后序节点中的val的vector
        std::vector<int> v;
        if(nullptr == root) return v;
        // 存储左路节点的栈
        std::stack<TreeNode*> st;
        // 存储后序节点的vector
        std::vector<TreeNode*> v_node;
        TreeNode* cur = root;
        // 如果树还有数据 || 栈还有左路节点,那么就继续
        while(cur || !st.empty())
        {
            // 入左路节点
            while(cur)
            {
                st.push(cur);
                cur = cur->left;
            }
            // 走到这里说明,左路节点走完了,需要走左路节点的右子树
            while(!st.empty())
            {
                // 取栈顶的数据
                TreeNode* top = st.top();
                // 如果栈顶数据的右子树已经被访问过了,那么此时直接将栈顶数据push到vector中,
                // 并把这个栈顶数据给pop掉,进行下一次循环
                // 我们在这里用了一个vector存储右子树的节点,只要这个vector的back()是栈顶数据的右子树
                // 那么就代表栈顶数据的右子树已经被访问过了
                if(!v_node.empty() && top->right == v_node.back())
                {
                    v.push_back(top->val);
                    v_node.push_back(top);
                    st.pop();
                    continue;
                }
                // 如果栈顶数据的右子树为空,那么就push到vector中
                if(!top->right)
                {
                    st.pop();
                    v.push_back(top->val);
                    v_node.push_back(top);
                }
                // 否则,就去入右子树的左路节点
                else
                {
                    cur = top->right;
                    break;
                }
            }
        }
        return v;
    }
};

2.6. 二叉树的层序遍历

102. 二叉树的层序遍历 - 力扣(LeetCode)

思路:将每一层的数据都push进队列中,队列的大小就是当前层数的个数(因为当pop掉cur层的最后一个数据时,那么也意味着cur的下一层的数据都进入了队列中,此时队列的大小就是当前层数的元素个数 )。通过每一层的个数控制每一次的循环次数

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        std::vector<std::vector<int>> vv;
        if(root == nullptr) 
            return vv;
        else
        {
            // 每层的元素个数
            int level_size = 0;
            std::queue<TreeNode*> qu;
            // 因为此时root != nullptr,因此直接push队列中
            qu.push(root);
            ++level_size;
            // 只要队列还有数据,就继续
           while(!qu.empty())
           {
                std::vector<int> v;
                while(level_size--)
                {
                    TreeNode* front =  qu.front();
                    qu.pop();
                    v.push_back(front->val);
                    if(front->left)
                        qu.push(front->left);
                    if(front->right)
                        qu.push(front->right);
                }
                // 当前层数的元素个数 = 队列的大小
                level_size = qu.size();
                vv.push_back(v);
           }
        }
        return vv;
    }
};

2.7. 二叉树的层序遍历Ⅱ

107. 二叉树的层序遍历 II - 力扣(LeetCode)

思路:这是4.4.的变形题,但是如果我们解决了4.4,那么这里就很简单了,我们按照4.4.的思路得到结果,只需要逆置下该结果即可。

class Solution {
public:
    vector<vector<int>> levelOrderBottom(TreeNode* root) {
        std::vector<std::vector<int>> vv;
        if(root == nullptr) 
            return vv;
        else
        {
            // 每层的元素个数
            int level_size = 0;
            std::queue<TreeNode*> qu;
            // 因为此时root != nullptr,因此直接push队列中
            qu.push(root);
            ++level_size;
            // 只要队列还有数据,就继续
           while(!qu.empty())
           {
                std::vector<int> v;
                while(level_size--)
                {
                    TreeNode* front =  qu.front();
                    qu.pop();
                    v.push_back(front->val);
                    if(front->left)
                        qu.push(front->left);
                    if(front->right)
                        qu.push(front->right);
                }
                // 当前层数的元素个数 = 队列的大小
                level_size = qu.size();
                vv.push_back(v);
           }
        }
        // 逆置结果即可
        std::reverse(vv.begin(),vv.end());
        return vv;
    }
};

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

236. 二叉树的最近公共祖先 - 力扣(LeetCode)

思路一:如果这是一个三叉链(每个节点有parent),我们可以转化为链表相交问题。但是很抱歉,这里并不是三叉链,因此这种解决方案不可行。

思路二:通过两个目标节点的所在位置进行判断:

case 1: 只要一个节点是根,那么根就是公共祖先

case 2: 如果一个在我的左子树,一个在我的右子树,那么我就是公共祖先

case 3: 如果两个都在我的左树,递归子问题(递归左树)

case 4: 如果两个都在我的右树,递归子问题(递归右树)

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

只有满二叉树或者完全二叉树我们可以认为高度是logN,因为会有歪脖子树

优化思路:如果是搜索二叉树可以优化到O(N)

一个比根小,一个比根大,根就是最近公共祖先

思路三:用DFS(在这里我们用前序遍历)求出p和q的路径并分别将从根节点到目标节点的路径入栈,将该问题转化为链表相交问题

注意:入栈可以依据前序的思想,如果遇到了就return true即可,如果没有遇到,递归左子树和递归右子树,如果左子树、右子树都为空,那么返回false

时间复杂度是O(N)

2.8.1.  第二种思路实现

class Solution {
public:
    bool is_exist_node(TreeNode* root, TreeNode* obj)
    {
        if(root == nullptr) 
            return false;
        else
        {
            return root == obj
            || is_exist_node(root->left,obj)
            || is_exist_node(root->right,obj);
        }
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        //只要一个节点是根,那么根就是最近公共祖先
        if(p == root || q == root) 
            return root;
        // 因为p不是根,因此p要么在左树,要么在右树
        bool p_left = is_exist_node(root->left,p);
        bool p_right = !p_left;
        // 同理, q要么在左树,要么在右树
        bool q_right = is_exist_node(root->right,q);
        bool q_left = !q_right;
        // 如果p在左且q在右 或者 p在右且q在左,那么最近公共祖先是root
        if((p_left && q_right) || (p_right && q_left))
            return root;
        // 如果p和q都在左树,那么递归左树
        if(p_left && q_left)
            return lowestCommonAncestor(root->left,p,q);
        // 如果p和q都在右树,那么递归右树
        else 
            return lowestCommonAncestor(root->right,p,q);
    }
};

2.8.2.  第三种思路实现

class Solution {
public:
    bool get_path(TreeNode* root, TreeNode* obj,std::stack<TreeNode*>& st)
    {
        // 依据前序的思想
        if(root == nullptr)
            return false;
        else
        {
            // 无脑入根
            st.push(root);
            // 如果找到了,就返回true
            if(root == obj)
                return true;
            bool ret = get_path(root->left,obj,st);
            // 如果没找到继续递归
            if(!ret)
            ret = get_path(root->right,obj,st);
            // 如果左右子树都找了且没找到,那么说明obj不在该路径,pop栈顶元素
            if(!ret)
                st.pop();
            return ret;
        }
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        std::stack<TreeNode*> p_st;
        std::stack<TreeNode*> q_st;
        get_path(root,p,p_st);
        get_path(root,q,q_st);
        // 将求最近公共祖先的问题转化了"链表相交"问题
        // "链表相交"问题: 长的先走差距步,然后同时走,相等就是相交节点
        while(p_st.size() != q_st.size())
        {
            if(p_st.size() > q_st.size())
                p_st.pop();
            else
                q_st.pop();
        }
        while(!p_st.empty() && !q_st.empty() && (p_st.top() != q_st.top()))
        {
            p_st.pop();
            q_st.pop();
        }
        return p_st.top();
    }
};

2.9. 二叉搜素树和双向链表

二叉搜索树与双向链表_牛客题霸_牛客网 (nowcoder.com)

思路:

利用中序的前驱节点和后继结点,

cur的前驱节点是prev,prev的后继节点是cur

即cur的left指向它的前驱节点;

如果前驱节点不为空,那么prev的后继节点指向cur

注意:cur需要保持唯一性(同一个prev),需要带引用。

例如:

class Solution {
public:
	void _tree_to_become_list(TreeNode* cur,TreeNode*& prev)
	{
		if(cur == nullptr)
			return ;
		else
		{
			// 依据中序的思想LNR
			_tree_to_become_list(cur->left, prev);
			// cur的left指向前驱节点prev,prev的right指向后继节点cur
			cur->left = prev;
			if(prev)
				prev->right = cur;
			prev = cur;
			_tree_to_become_list(cur->right, prev);
		}
	}

    TreeNode* Convert(TreeNode* pRootOfTree) {
        // 如果是空树,直接返回nullptr
		if(pRootOfTree == nullptr)
			return nullptr;
		TreeNode* prev = nullptr;
        _tree_to_become_list(pRootOfTree, prev);
		TreeNode* head = pRootOfTree;
        // 找链表的头节点
		while(head->left)
			head = head->left;
		return head;
    }

2.10. 二叉树创建字符串

题意:通过前序构造字符串。

注意,空节点要使用(),但是题干告诉我们有些括号会省略,但是如下情况不能省略:

case 1:如果左不为空,不能省略。

case 2:左树为空且右树不为空,括号不能省略。

case 3:右树不为空,括号不能省略

class Solution {
public:
    string tree2str(TreeNode* root) {
        if(root == nullptr)
            return "";
        else
        {
            std::string str = std::to_string(root->val);
            // if(root->left || root->right) 
            if(root->left != nullptr || (root->left == nullptr && root->right != nullptr))
            // 左树不为空 或者 左树为空且右树不为空, 左树的括号不能省略
            {
                str += '(';
                str += tree2str(root->left);
                str += ')';
            }
            // 右树不为空,右树的括号不能省略
            if(root->right)  
            {
                str += '(';
                str += tree2str(root->right);
                str += ')';
            }
            return str;
        }
    }
};

 二叉树进阶 - 下 done,有了这些铺垫,我们就可以了解平衡二叉搜索树了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值