题目
做题笔记
112 - Tree Summing
技巧:递归+树先序遍历
知识点:利用递归获得二叉树
做题记录:GOOGLE -> AC
方法一:刚开始不知道怎么读取树的数据,参考网上文章,首先分析树的遍历顺序(这里是先序遍历)以及树结点从数据中读取方法,然后利用递归读取树的数据。(辅助:cin.unget(),enum)
方法二:上一种方法边处理数据边读数据,这种方法先把完整的一个case数据读到string中,然后再处理。输入字符型数据注意:
cin >> char_a; 这里cin >> 会把空白字符忽略掉,即使char_a是char型数据。
cin.get();这里保留空白字符。括号匹配读取采用循环:
int num_left_bracket = 1; int num_right_bracket = 0; while (num_left_bracket != num_right_bracket) { cin >> c; input += c; if (c == '(') num_left_bracket++; if (c == ')') num_right_bracket++; }
优化:
- 这里想直接通过cin输入number,再通过cin输入char判断是否到达该行尾,经测试后发现不行,因为cin输入number后也处理了number的后一个非数字字符,也就是会把空格和换行符也处理掉,因此后续的cin输入char是无效的,始终只能一个一个字符读取并判断是否读取到换行符作为结束标志,或者先完整读取一行数据,再进行处理。
>
548 - Tree
知识点:已知中序序列和后序序列,构建二叉树。
技巧:
利用中序遍历和后序遍历构建树,利用后序遍历尾key值确定子树序列在中序遍历中的分界线。
树到根结点的路径求和,采用递归;做题记录:AC
优化:
利用中序和后序序列遍历时不构建二叉树,只用于遍历二叉树找最优解。优化后run time:0.652->0.558->
拓展:
已知中序序列和后序序列,构建二叉树(key值唯一)。
Node* build_treeinpost(const int* inorder, int inorder_start_index, int inorder_end_index, int num_node, const int* postorder, int postorder_start_index, int postorder_end_index) { // check the data length assert(postorder_end_index - postorder_start_index == inorder_end_index - inorder_start_index); // null node if (num_node == 0) return NULL; Node* temp = new Node; // get the key node index int inorder_index = inorder_start_index; while (inorder_index <= inorder_end_index && inorder[inorder_index] != postorder[postorder_end_index]) { inorder_index++; } temp->key = postorder[postorder_end_index]; // left child temp->lchild = build_treeinpost(inorder, inorder_start_index, inorder_index - 1, inorder_index - inorder_start_index, postorder, postorder_start_index, inorder_index - 1 - inorder_start_index + postorder_start_index); // right child temp->rchild = build_treeinpost(inorder, inorder_index + 1, inorder_end_index, inorder_end_index - inorder_index, postorder, inorder_index - inorder_start_index + postorder_start_index, postorder_end_index - 1); return temp; }
已知先序序列和中序序列,构建二叉树(key值唯一)。
Node* build_treeinpre(const int* inorder, int inorder_start_index, int inorder_end_index, int num_node, const int* preoder, int preorder_start_index, int preorder_end_index) { // check the data length assert(preorder_end_index - preorder_start_index == inorder_end_index - inorder_start_index); // null node if (num_node == 0) return NULL; Node* temp = new Node; // get the key node index int inorder_index = inorder_start_index; while (inorder_index <= inorder_end_index && inorder[inorder_index] != preoder[preorder_start_index]) { inorder_index++; } temp->key = preoder[preorder_start_index]; // left child temp->lchild = build_treeinpre(inorder, inorder_start_index, inorder_index - 1, inorder_index - inorder_start_index, preoder, preorder_start_index + 1, inorder_index - inorder_start_index + preorder_start_index); // right child temp->rchild = build_treeinpre(inorder, inorder_index + 1, inorder_end_index, inorder_end_index - inorder_index, preoder, inorder_index - inorder_start_index + preorder_start_index + 1, preorder_end_index); return temp; }
>
297 - Quadtrees
知识点:构建固定孩子数目的树。
技巧:
利用遍历构建树,递归。
做题记录:WA->AC
1.检查输入输出,未发现错误。
2.测试后发现程序未正确获得多个子树嵌套时该树左右界,修改BUG后获得AC。
>
712 - S-Trees
知识点:满二叉树快速索引到根结点。
技巧:
对于满二叉树,通过对每个结点进行判断并做移位运算可以快速找到叶子结点。
做题记录:WA->AC
>
699 - The Falling Leaves
知识点:二叉树建立
使用技巧
- 进入左孩子加一,进入右孩子减一,如此下去可以获得每个孩子的二叉树的左右偏离程度。
做题记录:WA->AC
1.检查输入输出格式,未发现异常。
2.检查初始化,增加数组长度防止溢出。
3.对比旧代码,发现移除了unget函数,查看文档发现,cin.unget(),只能退回一个字符,不能退回原来的数字(cin->num),num可能是多位字符的。提交后获得AC。确认是这个错误导致。优化
- 不开辟两个数组分别存储二叉树左右两端的和,而采用一个数组,起始位置设在整个数组中间防止左右溢出,再利用数组初始值0确定左右界限。
- 只做二叉树数据的读取并按照二叉树方法进行遍历,遍历过程中不断处理数据,避免建立二叉树并存储结点再进行后续处理。
上面两个优化后 run time :0.135->0.126
>
327 - Evaluating Simple C Expressions
知识点:字符串处理。
做题思路:1.遍历字母,找出前缀表达式和后缀表达式‘++’及‘–’,分别标记这些字母,并将这些字符置为空白字符。2.利用堆栈和标记求解加减和,输出时各字母结果时也使用标记。没有考虑用树结构完成。
做题记录:WA->AC
1.检查输入输出无问题,可能是空格影响前缀和周缀判断,修改算法,读入数据后先处理空格再追踪前缀后缀表达式。仍然错误。
2.后面通过加入数据输入测试发现是代码BUG。
>
839 - Not so Mobile
知识点:二叉树的构建和遍历
使用技巧
- 利用递归遍历构建二叉树,采用new结点方式进行。
- 通过增加结点数据,可记录结点到另一个结点的连线上的数据;
- 结点的一些数据生成可以再构建数的过程中获得,避免再次遍历处理数据;
做题记录:WA->AC
1.检查输出是否符合要求;
2.检查输入,加入特殊输入的数据进行测试,发现代码逻辑BUG;
修改上面第2点后再次提交获得AC,run time:0.175优化
销毁二叉树结点时,不采用递归(函数递归调用,本质是栈,深度优先)而采用迭代(使用队列queue,广度优先)进行,测试后run time:0.169 时间提升!
// 递归销毁树 void destroy_tree(Node* root) { if (root != NULL) { destroy_tree(root->lchild); destroy_tree(root->rchild); delete root; } } // 迭代销毁树 void destroy_tree_by_queue(Node* root) { if (root == NULL) return; Node* temp; queue<Node*> q; q.push(root); while (!q.empty()) { temp = q.front(); q.pop(); if (temp->lchild != NULL) q.push(temp->lchild); if (temp->rchild != NULL) q.push(temp->rchild); delete temp; } }
遍历二叉树判断结点从递归前序遍历改为采用非递归的层序遍历,这样遇到不满足情况结点时就可以立即返回,避免多余访问,测试后run time:0.166 时间提升!
拓展
- 遍历二叉树(辅助)的几种方法:
深度优先遍历(含递归和非递归版本):中序遍历、前序遍历、后序遍历
广度优先遍历:层序遍历
// 前序遍历,递归 void display_tree_preorder_by_recursive(Node* root) { if (root != NULL) { cout << root->lweight << " "; cout << root->ldistance << " "; cout << root->rweight << " "; cout << root->rdistance << " "; cout << "@ "; display_tree_preorder_by_recursive(root->lchild); display_tree_preorder_by_recursive(root->rchild); } } // 前序遍历,非递归方法一 void display_tree_preorder(Node* root) { if (root == NULL) return; Node* temp; stack<Node*> s; s.push(root); while (!s.empty()) { // get node first temp = s.top(); s.pop(); cout << temp->lweight << " "; cout << temp->ldistance << " "; cout << temp->rweight << " "; cout << temp->rdistance << " "; cout << "@ "; // push right child first if (temp->rchild != NULL) s.push(temp->rchild); // push left child second if (temp->lchild != NULL) s.push(temp->lchild); } } // 前序遍历,非递归方法二 void display_tree_preorder2(Node* root) { if (root == NULL) return; Node* temp = root; stack<Node*> s; while (temp || !s.empty()) { // trace left tree while (temp) { // get the node cout << temp->lweight << " "; cout << temp->ldistance << " "; cout << temp->rweight << " "; cout << temp->rdistance << " "; cout << "@ "; s.push(temp); temp = temp->lchild; } // trace right tree next time if (!s.empty()) { temp = s.top(); s.pop(); temp = temp->rchild; } } } // 中序遍历,递归 void display_tree_inorder_by_recursive(Node* root) { if (root != NULL) { display_tree_inorder_by_recursive(root->lchild); cout << root->lweight << " "; cout << root->ldistance << " "; cout << root->rweight << " "; cout << root->rdistance << " "; cout << "@ "; display_tree_inorder_by_recursive(root->rchild); } } // 中序遍历,非递归 void display_tree_inorder(Node* root) { if (root == NULL) return; Node* temp = root; stack<Node*> s; while (temp || !s.empty()) { if (temp) { // push left child for node temp s.push(temp); temp = temp->lchild; } else { // get top node temp = s.top(); s.pop(); cout << temp->lweight << " "; cout << temp->ldistance << " "; cout << temp->rweight << " "; cout << temp->rdistance << " "; cout << "@ "; // check right child next time temp = temp->rchild; } } } // 后序遍历,递归 void display_tree_postorder_by_recursive(Node* root) { if (root != NULL) { display_tree_postorder_by_recursive(root->lchild); display_tree_postorder_by_recursive(root->rchild); cout << root->lweight << " "; cout << root->ldistance << " "; cout << root->rweight << " "; cout << root->rdistance << " "; cout << "@ "; } } // 后序遍历,非递归方法一 void display_tree_postorder(Node* root) { if (root == NULL) return; Node* temp = root; Node* q = NULL; stack<Node*> s; while (temp || !s.empty()) { // trace left tree while (temp) { s.push(temp); temp = temp->lchild; } if (!s.empty()) { temp = s.top(); if (temp->rchild == NULL || temp->rchild == q) { // get top node cout << temp->lweight << " "; cout << temp->ldistance << " "; cout << temp->rweight << " "; cout << temp->rdistance << " "; cout << "@ "; // remember the node for next time q = temp; s.pop(); // set temp node null temp = NULL; } else { // trace right tree next time temp = temp->rchild; } } } } // 后序遍历,非递归方法二 void display_tree_postorder2(Node* root) { if (root == NULL) return; Node* temp = NULL; stack<Node*> traverse; traverse.push(root); stack<Node*> visit; // trace all node while (!traverse.empty()) { temp = traverse.top(); visit.push(temp); traverse.pop(); if (temp->lchild) traverse.push(temp->lchild); if (temp->rchild) traverse.push(temp->rchild); } // get the node while(!visit.empty()) { temp = visit.top(); visit.pop(); cout << temp->lweight << " "; cout << temp->ldistance << " "; cout << temp->rweight << " "; cout << temp->rdistance << " "; cout << "@ "; } } // 层次遍历 void display_tree_levelorder(Node* root) { if (root == NULL) return; Node* temp = NULL; queue<Node*> q; q.push(root); while (!q.empty()) { temp = q.front(); cout << temp->lweight << " "; cout << temp->ldistance << " "; cout << temp->rweight << " "; cout << temp->rdistance << " "; cout << "@ "; q.pop(); if (temp->lchild) q.push(temp->lchild); if (temp->rchild) q.push(temp->rchild); } }
>
10562 - Undraw the Trees
知识点:将多节点的树构建成二叉树并进行遍历
使用技巧
- 由于树是多节点树,构建的二叉树左孩子指向最左边的孩子结点,若无孩子则指向NULL;右孩子指向相邻右边的兄弟结点,若无兄弟则指向NULL。(孩子兄弟表示法)
- 孩子兄弟表示法表示多结点树而构建的二叉树可对多结点树进行中序遍历。
做题记录:WA
1.检查输出是否符合要求;
2.检查输入,加入特殊输入的数据进行测试;
3.加入空树情况的特殊处理;访问内存的初始化工作(避免多case影响)。
修改上面后再次提交仍然WA
4.根据题目严格遵守有效字符要求,加入判断语句。
待解决BUG。