两个面试案例

欢迎访问我的博客首页


1. 把字符串转换成整数


1. 初始化列表与构造函数体

  使用初始化列表初始化成员变量时,按成员变量的声明顺序初始化,与初始化列表中的顺序无关。在构造函数体内初始化成员变量时,才按初始化语句的顺序初始化。

2. 实现 atoi 函数

  题目:实现 atoi 函数。输入的字符串可能包含 ‘0’ 到 ‘9’ 之内的字符和符号字符 ‘+/-’,还可能包含其它无效字符。atoi 函数只处理无效字符前的部分,比如输入字符串 “123ab45”,输出整数 123。如果整数越界,正数输出 32 位有符号最大值,负数输出 32 位有符号最小值。
  分析:C++ 中的 atoi 函数的参数是 char * 类型,它是把字符转换成整数。如果字符包含小数点等其它非法字符,它只处理非法字符前面的部分。

bool gb_inputValid = true;
int my_atoi(string str) {
	gb_inputValid = true;
	// 1.是否为空或只含空格。
	if (str.size() == 0 || str.find_first_not_of(" ") == string::npos) {
		gb_inputValid = false;
		return 0;
	}
	// 2.去首尾空格。
	str.erase(0, str.find_first_not_of(" "));
	//str.erase(str.find_last_not_of(" ") + 1);
	// 3.符号位。
	bool flag = true;
	if (str[0] == '-' || str[0] == '+') {
		if (str[0] == '-')
			flag = false;
		str.erase(0, 1);
	}
	// 4.去掉符号位后面的0。
	str.erase(0, str.find_first_not_of('0'));
	// 5.截取符号位后面的有效部分。
	int len = 0;
	for (; len < str.size(); len++) {
		if (str[len] < '0' || str[len] > '9')
			break;
	}
	// 6.处理有效部分。
	if (len == 0) {
		gb_inputValid = false;
		return 0;
	}
	str = str.substr(0, len);
	int sum = 0, index = 0;
	while (len > 0) {
		// 7.x本身是否溢出。
		if (index > 9 || (index == 9 && str[len - 1] - '0' > 2)) {
			if (flag == true)
				return INT_MAX;
			return INT_MIN;
		}
		int x = (str[len - 1] - '0') * pow(10, index);
		// 8.加上x是否溢出。
		if (flag == true) {
			if (x >= INT_MAX - sum)
				return INT_MAX;
		}
		else {
			if (x - 1 >= INT_MAX - sum)
				return INT_MIN;
		}
		sum += x;
		index++;
		len--;
	}
	// 9.带符号的结果。
	if (flag == false) {
		sum *= -1;
	}
	return sum;
}

  代码:这道题的题目给出了需要考虑的情况,即使没有给出这些提示,程序员也要考虑到。其次要注意越界情况:第 7 部分首先判断 x 是否溢出,第 8 部分再判断加上 x 后是否溢出,这两处溢出判断不能遗漏。

2. 树中两个节点的最低公共祖先


  对三种树中的两个节点求最低公共祖先。

2.1 二叉排序树


  当树是二叉排序树时问题很简单,可以根据二叉排序树的性质找两个节点的最低公共祖先。

二叉排序树
图 2.1 图 2.1 2.1

1. 先序遍历法

  二叉排序树中两个节点 a 和 b 的最低公共祖先 p 的值介于这两个节点之间。假设 a<b,使用先序遍历找到的第一个满足 a < p < b 的节点 p 就是 a 和 b 的 最低公共祖先。
  比如找 a=8 和 b=16 的节点,先序遍历到第一个满足 a < p < b 的节点 p=15 就是 a 和 b 的最低公共祖先。下面是使用递归实现的先序遍历算法:

void pre_rescurse(Node* tree, int a, int b) {
	if (tree == nullptr)
		return;
	if (tree->data > a && tree->data < b) {
		cout << tree->data << " ";
		return;
	}
	pre_rescurse(tree->lchild, a, b);
	pre_rescurse(tree->rchild, a, b);
}

2. 二叉排序树的查找法

  其实只要从树根开始找到第一个满足 a < p < b 的节点 p 就可以,其过程就是从二叉排序树中查找一个满足 a < p < b 的节点 p :

Node* find(Node* tree, int a, int b) {
	while (tree != nullptr) {
		if (tree->data < a)
			tree = tree->rchild;
		else if (tree->data>b)
			tree = tree->lchild;
		else
			return tree;
	}
	return nullptr;
}

2.2 节点带有指向父节点指针的树


  如果树的每个节点有一个指向父节点的指针,找最低公共祖先的问题可以转化为找两个链表的第一个交点的问题,也很简单。同时这也不要求树是二叉树。

节点带有指向父节点指针的树
图 2.2 图 2.2 2.2

  如图 2.2,找节点 D 和 节点 H 的最低公共祖先,等价于找链表 D->B->A 与 链表 H->E->B->A 的第一个交点。找两个链表的第一个交点可以使用栈,还可以使用跑路法。使用栈的方法很容易理解,这里介绍一下跑路法。跑路法就是让两个指针从两个链表头部开始遍历链表,当一个指针到达链表尾部时从另一个链表头部开始继续遍历,直到两个链表所指节点相同。比如指针 1 从 D 节点开始遍历的路径是 D->B->A->H->E->B…,指针 2 从 H 节点开始遍历的路径是 H->E->B->A->D->B…。当它们各遍历 6 个元素时都指向了节点 B,于是节点 B 就是它们的第一个交点,也就是它们的最低公共祖先。

2.3 普通树


普通的树
图 2.3 图 2.3 2.3

1. 查找法

  还是假设要找节点 D 和节点 H 的最低公共祖先。从树根 A 开始查找节点 D 和节点 H,可以使用任何遍历算法。如果都能查找到,则最低公共祖先在该子树中;如果只包含节点 D 或节点 H 则该节点的父节点就是节点 D 和节点 H 的最低公共祖先。
  比如遍历到 A 时,在以 A 为根节点的子树中可以查找到节点 D 和节点 H。然后遍历节点 B,在以 B 为根结点的子树中也可以查找到节点 D 和节点 H,于是无须考虑节点 C。然后遍历节点 D,在以 D 为根节点的树中只能发现节点 D,说明节点 H 必在以节点 D 的兄弟节点为根节点的子树中,于是节点 D 的父节点 B 就是节点 D 和节点 H 的最低公共祖先。

int find_node(treeNode* tree, int a, int b) {
	if (tree == nullptr) return 0;
	int res_a = 0, res_b = 0;
	queue<treeNode*> qu;
	qu.push(tree);
	while (qu.empty() != true) {
		treeNode* temp = qu.front();
		qu.pop();
		if (temp->data == a) res_a = 1;
		if (temp->data == b) res_b = 1;
		for (auto it = temp->children.begin(); it != temp->children.end(); it++)
			qu.push(*it);
	}
	return res_a + res_b;
}

treeNode* find_tree(treeNode* tree, int a, int b) {
	if (tree == nullptr) return nullptr;
	if (find_node(tree, a, b) != 2) return nullptr;
	while (tree != nullptr) {
		for (auto it = tree->children.begin(); it != tree->children.end(); it++) {
			int count = find_node(*it, a, b);
			if (count == 2) {
				tree = *it;
				break;
			}
			else if (count == 1)
				return tree;
		}
	}
	return nullptr;
}

  int find_node(treeNode* tree, int a, int b) 函数使用层序遍历,在以 tree 为根节点的树中查找是否包含值为 a, b 的节点。不包含返回 0,包含两者之一返回 1,包含两者返回 2。
  treeNode* find_tree(treeNode* tree, int a, int b) 函数用于查找最低公共祖先。首先确保以 tree 为根节点的树中包含值为 a, b 的节点,否则返回空指针。然后在它的子树中继续查找。

2. 求最后一个交点法

  上面的查找法对有些节点做了多次遍历,比如以 A 为根节点遍历了一遍树,然后以 B 为根节点又遍历了一遍树,这时以 B 为根节点的树的节点已经被遍历了两次。下面是另一种思路,如图 2.3 的右图。
  与 2.2 节求两个链表的第一个交点不同,此时是求两个链表的最后一个交点。求两个链表的最后一个交点是很容易的,关键是怎么从树中找出这两个链表,也就是从树中查找一个节点并把路径保存下来。

bool get_path(treeNode* tree, int node, list<treeNode*>& path) {
	if (tree->data == node) return true;
	path.push_back(tree);

	bool found = false;
	for (auto it : tree->children) {
		found = get_path(it, node, path);
		if (found == true)
			break;
	}
	if (found == false)
		path.pop_back();
	return found;
}

treeNode* get_last_common_node(list<treeNode*>& path1, list<treeNode*>& path2) {
	treeNode *p = nullptr;
	for (auto it1 = path1.begin(), it2 = path2.begin(); it1 != path1.end() && it2 != path2.end(); it1++, it2++) {
		if (*it1 != *it2) break;
		p = *it1;
	}
	return p;
}

int main() {
	treeNode* tree = build_tree();
	list<treeNode*> path1;
	list<treeNode*> path2;
	get_path(tree, 6, path1);
	get_path(tree, 5, path2);
	treeNode* res = get_last_common_node(path1, path2);
	system("pause");
}

  函数 get_path 使用递归的方法获取从出发点到它的某个指定的子孙结点的路径。

3. 参考


  1. 二叉树和普通树
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值