题意:
判断读入的 LISP S-expressions 表达式是否存在一条从根到叶子节的路径, 使这条路径上所有节点的和为 I.
1. I 与表达式 T 是成对出现的, 但是 T 有可能是跨行的.
2. 所有节点的出现形式都为 (integer()()), 如果有左/右子树, 则在 integer 的左/右括号里.
思路:
1. 由于表达式是跨行的, 所以需要先把表达式完整的读入, 使用 getline(cin, temp, ')'); 进行读入, 即读到 ) 就停止, 然后判断到这里为止是否左右括号完全匹配, 若已匹配完毕, 则说明表达式读入完毕.
2. 根据表达式建树.由题意(2)可知, 每个节点都包含一个整数及由两个括号包裹起来的左/右子树. 所以建树过程可以递归如下:
(1). 先获取读取到的第一个整数, 做为节点的值(注意有可能有负数存在)
(2). 由整数往后, 找到第一个使括号完全匹配的字符串, 作为左子树; 第二个使括号完全匹配的字符串, 作为右子树.
(3). 递归创建左子树及右子树.若括号匹配到的字符串为空, 说明左/右子树为空.
(4). 根据左/右子树创建当前节点.
3. 判断是否存路径满路和为 I, 同样使用递归:
(1). 判断当前节点是否为叶子节点, 且值与 I 相等, 若相等, 则返回 true.
(2). 若不等, 则在其左/右子树中递归查找.
(3). 只要左右子树有一边能查到, 就为 true.
要点:
1. 这里的 tree 的 Node 的成员变量都是直接 public 的, 虽然违反面向对象设计原则, 但是对于一个算法题, 还要弄一堆 get/set 就太过麻烦了;
2. memset(tmp, '\0', 10);
3. string substr() 函数不填第二个参数, 表示取到字符串最后.
代码:
# include <iostream>
# include <string>
# include <cstdio>
# include <cstring>
# include <vector>
# include <algorithm>
# include <cctype>
# include <iterator>
# include <assert.h>
# include <stack>
using namespace std;
// 所有的输入都是有效的, 但是有可能会存在于多行中, 每读入完一个 int,
// 就按 ) 分隔的 getline 进行读入, 直到 ( 与 ) 完全匹配, 表示一个表达式读入完毕
class Node {
public:
Node(int n, Node* left, Node* right):
n_(n), left_(left), right_(right){}
int n_;
Node* left_;
Node* right_;
};
// 从 pos 开始,找到第一个合法的 express 及其所在下标, 即左右括号必须匹配
pair<string, int> getValidExpression(const string& expression, int startPos) {
int cntLeft = 0;
int cntRight = 0;
int endPos = startPos;
for (int i=startPos; i<expression.size(); i++) {
if (expression[i] == '(') {
++cntLeft;
} else if (expression[i] == ')') {
++cntRight;
if (cntLeft == cntRight) {
endPos = i + 1;
break;
}
}
}
return make_pair(expression.substr(startPos, endPos - startPos),
endPos);
}
// 获取节点的数字,即找到的第一个整数,返回整数及其最后位所在的下标
// 如果找不到数字, 说明为空, 返回 <-1, -1>
// 这里真正有效的是 second 中的 -1
pair<int, int> getNodeNumber(const string& expression) {
char tmp[10];
memset(tmp, '\0', 10);
int j = 0;
int pos = 0;
bool begin = false;
for (int i=0; i<expression.size(); i++) {
// digit 有可能是负数
if (isdigit(expression[i]) || expression[i] == '-') {
tmp[j++] = expression[i];
begin = true;
} else {
if (begin) {
pos = i;
break;
}
}
}
// 如果没有开始,说明没找到数字, 真正有效的是 second 中的 -1
if (!begin) return make_pair(-1, -1);
return make_pair(atoi(tmp), pos);
}
// 根据读入的 expression 创建树, 返回根节点
// expression 中可能包含空格与回车换行符
Node* buildTree(const string& expression) {
pair<int, int> number = getNodeNumber(expression);
if (number.second == -1) return NULL;
// 不能拿 first 来判断,不然 -1 (-1()()) 的 first 正好是 -1
// if (number.first == -1) return NULL;
pair<string, int> left = getValidExpression(expression, number.second);
pair<string, int> right = getValidExpression(expression, left.second);
Node* leftTree = buildTree(left.first);
Node* rightTree = buildTree(right.first);
return new Node(number.first, leftTree, rightTree);
}
// 判断 tree 是否存在一条路径的和为 sum
// 往左右子树查找, 只要有一边找到, 就为真
bool isPathExist(const Node* root, int sum) {
if (root == NULL) return false;
if (root->left_ == NULL &&
root->right_ == NULL &&
root->n_ == sum) return true;
// bool 值要先初始化
bool left = false;
bool right = false;
if (root->left_ != NULL) {
left = isPathExist(root->left_, sum - root->n_);
}
if (root->right_ != NULL) {
right = isPathExist(root->right_, sum - root->n_);
}
return (left || right);
}
// 判断一个表达式是否为合法, 即其左右括号必须匹配
bool isValid(const string& expression) {
if (expression.empty()) return false;
int cntLeft = 0;
int cntRight = 0;
for (int i=0; i<expression.size(); i++) {
if (expression[i] == '(') {
++cntLeft;
} else if (expression[i] == ')') {
++cntRight;
}
}
return (cntLeft == cntRight);
}
// 释放树空间
void freeTree(Node* root) {
if (root == NULL) return;
freeTree(root->left_);
freeTree(root->right_);
delete root;
}
int main(int argc, char const *argv[])
{
#ifndef ONLINE_JUDGE
freopen("112_i.txt", "r", stdin);
freopen("uva_o.txt", "w", stdout);
#endif
int sum;
while (!cin.eof()) {
cin >> sum;
string expression; // 存放一个合法的表达式
while (!cin.eof()) {
string temp;
getline(cin, temp, ')');
expression = expression + temp + ")";
if (isValid(expression)) break;
}
if (isValid(expression)) {
Node* root = buildTree(expression);
if (isPathExist(root, sum)) {
cout << "yes" << endl;
} else {
cout << "no" << endl;
}
freeTree(root);
}
}
return 0;
}
环境: C++ 4.5.3 - GNU C++ Compiler with options: -lm -lcrypt -O2 -pipe -DONLINE_JUDGE
参考: 比较简明的解法, 不用建树 http://blog.sina.com.cn/s/blog_64018c250100qjrt.html