《剑指Offer》面试题7:重建二叉树

题目:题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2, 4, 7, 3, 5, 6, 8}和中序遍历序列{4, 7, 2, 1, 5, 3, 8, 6},则重建出图2.6所示的二叉树并输出它的头结点。

1. 解题思路

        要构建一颗二叉树,一定要有中序遍历,再加上先序或者后序遍历,然后按照:
(1) 先序遍历中第一个元素即为根节点root;
(2) 在中序遍历中查找根节点root的位置,将根节点左边的划分为左子树left,右边的为右子树right;
(3) 如果left不空,则令root->left = left,同理,right不空则令root->right = right;
(4) 重复(1)(2)(3),建立左子树和右子树。

        整个过程是一个递归的过程,在“牛客网”上的ACE代码:

TreeNode * reConstructBinaryTree(std::vector<int> pre, std::vector<int> vin)
{
	// 空的,或为叶节点,或为一颗空树
	if (pre.empty() || vin.empty())
		return nullptr;

	// 先序遍历中的第一个元素就是根节点
	TreeNode* root = new TreeNode(pre.front());

	// 只有一个节点了
	if (pre.size() == 1 && pre.front() == vin.front())
		return root;

	// 根节点在中序遍历中的位置
	std::vector<int>::iterator rootIndex = std::find(vin.begin(), vin.end(), root->val);
	// 如果在中序遍历序列中找不到,则抛出异常
	if (rootIndex == vin.end() && *rootIndex != root->val)
		return nullptr;

	// 计算左子树中节点总个数
	int leftCount = rootIndex - vin.begin();
	// 计算右子树中节点总个数
	// ATTENTION:vin.end()指向的是最后一个元素的后一个位置,所以要再减1
	int rightCount = vin.end() - rootIndex - 1;

	if (leftCount > 0)
	{
		// std::vector<int> vec(v.begin(), v.end()),这里不包括v.end()这个元素,也即vec = [v.begin(), v.end());故末尾要加1
		std::vector<int> newPreLeft(pre.begin() + 1, pre.begin() + leftCount + 1);
		std::vector<int> newInLeft(vin.begin(), rootIndex);
		root->left = reConstructBinaryTree(newPreLeft, newInLeft);
	}

	if (rightCount > 0)
	{
		std::vector<int> newPreRight(pre.begin() + leftCount + 1, pre.end());
		std::vector<int> newInRight(rootIndex + 1, vin.end());
		root->right = reConstructBinaryTree(newPreRight, newInRight);
	}
	return root;
}

再加一个测试程序:

// 中序遍历,通过v_inOrderPrint返回遍历结果
void Solution::PrintTree(TreeNode * pRoot, std::vector<int>& v_inOrderPrint)
{
	if (nullptr == pRoot)
		return;

	PrintTree(pRoot->left, v_inOrderPrint);
	v_inOrderPrint.push_back(pRoot->val);
	PrintTree(pRoot->right, v_inOrderPrint);
}

// test
int main()
{
	std::vector<int> preorder{ 1, 2, 4, 7, 3, 5, 6, 8 };
	std::vector<int> inorder = { 4, 7, 2, 1, 5, 3, 8, 6 };
	
	Solution* s = new Solution();

	TreeNode* root = s->reConstructBinaryTree(preorder, inorder);
	std::vector<int> v_output;// 存放中序遍历的结果
	s->PrintTree(root, v_output);

	//for (auto& v : v_output)
	//	printf("%d ", v);

	if (v_output == inorder)
		printf("Pass!");
	else
		printf("Failed!");

	return 0;
}
2. 知识笔记
  • v.end()所指的位置: 最后一个元素的后一个位置,比如有一个vector数组std::vector< int > v{4, 7, 2, 1, 5, 3, 8, 6}的存储结构示意图如下表:
    v下标01234567
    v值47215386
    v.begin() = v[0] = 4, v.end()是v[7] = 6的后一个位置,相当于v[8]。
  • const在不同地方的含义:(摘自《Effective C++(第三版)》“条款03”)
char greeting[] = "hello";
char* p = greeting;				// non-const pointer, non-const data
const char* p = greeting;		// non-const pointer, const data
char const *p = greeting;		// 同const char* p
char* const p = greeting;		// const pointer, non-const data
const char* const p = greeting;	// const pointer, const data

// 类似T* const,即const pointer, non-const data
const std::vector<int>::iterator iter = v.begin();
// 类似const T*,即non-const pointer, const data
std::vector<int>::const_iterator cIter = v.begin();	

总结:如果关键字const出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自身是常量(左对象,右指针)。对于迭代器,把iterator看作是星号,刚好与前面的相反(这个是最容易记错的。。。)

再看看const在函数的不同位置处的含义:

class TextBlock{
public:
	// Constructor
	explicit TextBlock(std::string& _text): text(_text) {}
	
	// 返回的是一个const类型的std::size_t对象
	const std::size_t GetTextLength();
	// 返回的是一个const类型的std::size_t对象
	// 最后的const修饰的是调用这个函数的对象
	const std::size_t GetTextLength() const;
private:
	std::string text;
};

其中const std::size_t GetTextLength()返回的是一个const类型的std::size_t对象;而const std::size_t GetTextLength() const返回类型与前者相同,最后的const修饰的是调用该函数的对象,也就意味着在这个函数体内部,不允许更改成员变量的值。试想一个这样的情况:文本块只存放一行的数据(81个字符),而对超出的部分进行舍弃,根据这个场景我们来实现一下上面的函数:

const std::size_t MAX_LENGTH = 81;		// 一行的长度
const std::size_t TextBlock::GetTextLength()
{
	if(text.length() > MAX_LENGTH)
		text = text.substr(0, MAX_LENGTH);
	return text.length();
}

这样没问题,但是当我们实现const std::size_t TextBlock::GetTextLength() const函数时,就会有错误了:在该函数体内不允许对成员变量text进行修改。要想让程序通过编译,需要将成员变量text的类型变成mutable就可以了,也就是mutable std::string text

OK,上面解释了函数末尾带const和不带const的区别,下面再解决一个问题:代码复用。现在有两个新的成员函数:const char& operator[] (std::size_t pos) constchar& operator[] (std::size_t pos),这两个函数的函数体内不需要做任何修改,但是要做一大堆的验证,而这一大堆验证在两个函数内都需要,为避免重复写这段代码,可以这样解决:

const char& TextBlock::operator[](std::size_t pos) const
{
	// ...
	// 一大堆验证
	// ...
	return text[pos];
}

char& TextBlock::operator[](std::size_t pos)
{
	return const_cast<char&>(			// 将返回类型的const属性转除
		static_cast<const TextBlock&>(*this)[pos]  // 为*this加上const,调用上面那个函数
		);
}

注意,上面调用的顺序不要乱了(变量调用常量const,因为常量安全性高)!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值