Cracking the Coding Interview(Trees and Graphs)
Cracking the Coding Interview(Trees and Graphs)
树和图的训练平时相对很少,还是要加强训练一些树和图的基础算法。自己对树节点的设计应该不是很合理,多多少少会有一些问题,需要找一本数据结构的书恶补一下如何更加合理的设计节点。
class TreeNode { public : int treenum; TreeNode** children; int child_num; int child_len; int depth; int iterator; TreeNode* rightchild; TreeNode* leftchild; TreeNode* father; TreeNode():child_num(0),depth(0),iterator(0),child_len(10) { children = new TreeNode*[child_len]; rightchild = NULL; leftchild = NULL; } ~TreeNode() { delete [] children; } void addchild(TreeNode* tn) { children[child_num++] = tn; } int isleaf() { if (child_num == 0) { return TRUE; } else { return FALSE; } } int hasnextchild() { if (iterator < child_num) { return TRUE; } else { return FALSE; } } TreeNode* nextchild() { return children[iterator++]; } protected : private : }; |
1.Implement a function to check if a tree is balanced.For the purposes of this question,a balanced tree is defined to be a tree such that no two leaf nodes differ in distance from the root by more than one.
(1)分别记录最深的叶子节点的高度和最浅的叶子节点的高度即可。以下是一种实现,利用队列对树进行广度的遍历...遍历的过程中记录最高的高度和最低的高度。
int isbalanced(TreeNode* root) { queue<TreeNode*>* q = new queue<TreeNode*>; int max = 0,min = BIGNUM; root->depth = 0; q->push(root); while (!q->empty()) { TreeNode* tn = q->front(); q->pop(); if (tn->isleaf() == TRUE) { min = tn->depth<min?tn->depth:min; max = tn->depth<max?max:tn->depth; } else { while (tn->hasnextchild() == TRUE) { TreeNode* child = tn->nextchild(); child->depth = tn->depth + 1; q->push(child); } } } if (max - min > 1) { return FALSE; } else { return TRUE; } } |
(2)其实求解树的高度不必要进行这样的广度遍历,只需要递归的实现求解树的高度即可。树的结构很适合递归程序,算法也优先考虑是否有递归的实现,这样能够保证代码的简洁。
2.Given a directed graph,design an algorithm to find out whether there is a route between two nodes.
(1)图在程序中的表示方法也有邻接矩阵,邻接链表的形式,也可以直接表示为图的节点的连接形式,这样就需要合理的设计图的节点单元。以下程序的实现考虑邻接矩阵形式表示的有向图。
图中如果存在环,因为邻接矩阵中无法标记节点是否被访问,所以程序实现过程中手动修改一些矩阵元素,保证程序不会再环中无限递归调用。
int isroute( int src, int dst, int ** grp, int length) { int * srcarray = grp[src]; if (srcarray[dst] == 1) return TRUE; else { for ( int i = 0;i < length;i++) { if (srcarray[i] == 1&&i != src) { //indicate has visit srcarray[i] = 0; return isroute(i,dst,grp,length); } } } return FALSE; } |
(2)当然可以用更加普通的一些函数抽象来设计程序,将图的节点单元抽象化。
3.Given a sorted(increased order) array,write an algorithm to create a binary tree with minimal height.
(1)因为最小高度的二叉树,和有序几乎没有什么关系,生成一个完全二叉树即可满足最小高度。
TreeNode* createNode( int * array, int length) { TreeNode* root = new TreeNode(); int index = 0; queue<TreeNode*>* q = new queue<TreeNode*>(); root->treenum = array[index++]; q->push(root); while (index < length) { TreeNode* tn = q->front(); q->pop(); TreeNode* l = new TreeNode(); l->treenum = array[index++]; tn->leftchild = l; if (index > length) break ; TreeNode* r = new TreeNode(); r->treenum = array[index++]; tn->rightchild = r; q->push(tn->rightchild); q->push(tn->leftchild); } return root; } |
(2)当然我们也可以生成一个左边子树小于右边子树的二叉树。
4.Given a binary search tree,design an algorithm which creates a linked list of all the nodes at each depth(ie,if you have a tree with depth D,you'll have D linked lists).
(1)仅仅需要实现一个广度优先搜索的变种,因为广度优先搜索的时候可以把节点的高度信息记录下来,这样把不同高度的节点加入不同的链表即可
TreeNode*** backnodelist(TreeNode* root) { TreeNode*** tn = new TreeNode**[20]; int index[20] = {0}; for ( int i = 0;i < 20;i++) { tn[i] = new TreeNode*[20]; } for ( int i = 0;i < 3;i++) { for ( int j = 0;j < 10;j++) { tn[i][j] = NULL; } } queue<TreeNode*>* q = new queue<TreeNode*>(); root->depth = 0; q->push(root); while (!q->empty()) { TreeNode* outtn = q->front(); q->pop(); tn[outtn->depth][index[outtn->depth]++] = outtn; if (outtn->rightchild != NULL) { outtn->rightchild->depth = outtn->depth+1; q->push(outtn->rightchild); } if (outtn->leftchild != NULL) { outtn->leftchild->depth = outtn->depth+1; q->push(outtn->leftchild); } } return tn; } |
(2)
5.Write an algorithm to find the 'next' node(ie,in-order successor) of a given node in a binary search tree where each node has a link to its parent.
(1)in-order序是访问左边节点,访问当前节点,访问右边节点。有了指向父节点的指针的话算法实现比较容易,只需要一些简单逻辑的判断。所以这个题目的重点在于树的几种遍历顺序。
TreeNode* nextnode(TreeNode* node) { if (node->rightchild->treenum != 0) { return node->rightchild; } else { if (node == node->father->rightchild) { return node->father->father; } else { return node->father; } } } |
6.Design an algorithm and write code to find the first common ancestor of two nodes in a binary tree.Avoid storing additional nodes in a data structure.Note:This is not necessarily a binary search tree.
这个题目记得师兄跟我提起过,说面试的时候问到他这个题目,当时我想其实这个问题挺简单的...一定很容易。但是在我实现的时候就遇到了困难...搞不定这个题目。
(1)如果这个题目跟上个题目类似,存在子节点到父节点的指针就比较简单了,从两个节点开始向上层搜索,第一次同时遇到的父节点必定是第一个共同的祖先。但是这里仅仅是描述,实现的时候应该还需要考虑更多的问题,题目要求不能使用额外的存储节点的数据结构,所以这里回溯的时候应该是一个一步的回溯,另外一个每次回溯至根节点,检测是否遇到,如果遇到则为第一个共同的祖先,所以这里的时间复杂度应该是O(n*m),n为一个节点到共同祖先的距离,m是另外一个节点的深度。
(2)另外一个直观的想法是深搜,然后将已经遍历的节点存储在一个堆中,如果找到其中一个节点,不断的退出堆,在进行深搜,如果找到另外一个节点,该节点为共同祖先。但是深搜的算法不知道如何实现,还要利用堆,这个违背了题目中的限定条件,所以仅仅是一个思路。
(3)
递归的思想。如果节点p和q再root节点的同一侧,则继续递归至那一侧寻找first common ancestor,如果节点p和q再root节点的两侧,则该root节点及为寻找的first common ancestor。
//copy from Cracking the coding interview public Tree commonAncestor(Tree root,Tree p,Tree q) { if (covers(root.left,p)&&covers(root.left,q)) return commonAncestor(root.left,p,q) if (covers(root.right,p)&&covers(root.right,q)) return commonAncestor(root.right,p,q)) return root; } private boolean covers(Tree root,Tree p) { if (root == null ) return false ; if (root == p) return true ; return covers(root.left,p) || covers(root.right,p); } |
(4)
//copy from cracking the coding interview static int TWO_NODES_FOUND= 2 ; static int ONE_NODES_FOUND= 1 ; static int NO_NODES_FOUND= 0 ; int covers(TreeNode root,TreeNode p ,TreeNode q) { int ret = NO_NODES_FOUND; if (root == null ) return ret; if (root == p || root == q) ret += 1 ; ret += covers(root.left,p,q); if (ret == TWO_NODES_FOUND) return ret; return ret + covers(root.right,p,q); } TreeNode commonAncestor(TreeNode root,TreeNode p,TreeNode q) { if (q == p&&(root.left == q|| root.right == q)) return root; int nodesFromLeft = covers(root.left,p,q); if (nodesFromLeft == TWO_NODES_FOUND) { if (root.left == p || root.left == q) return root.left; else return commonAncestor(root.left,p,q); } else if (nodesFromLeft == ONE_NODES_FOUND) { if (root == p) return p; else if (root == q) return q; } int nodesFromRight = covers(root.right,p,q); if (nodesFromLeft == TWO_NODES_FOUND) { if (root.right== p || root.right== q) return root.right; else return commonAncestor(root.right,p,q); } else if (nodesFromRight == ONE_NODES_FOUND) { if (root == p) return p; else if (root == q) return q; } if (nodesFromLeft == ONE_NODES_FOUND&&nodesFromRight == ONE_NODES_FOUND) return root; else return null ; |
7.You have two very large binary trees:T1,with millions of nodes,and T2,with hundreds of nodes.Create an algorithm to decide if T2 is a subtree of T1.
这个题目我看到之后没有任何思路,T1的节点数量太多,直接吓到了。下面的思路均来自答案。
(1)其中一种思路是利用字符串表示这里的树,及利用其中一种遍历树的顺序将树转换为字符串。无论什么顺序,只要两个树的遍历顺序相同即可。如果得到的字符串T2是T1的子串,则可以说明T2就是T1的子树。(子串的检测可以利用suffix tree,suffix tree, 或 后缀树,是一种相当神奇的数据结构,它包含了字符串中的大量信息,能够于解决很多复杂的字符串问题 —— 事实上,基本上目前为止俺遇到过的所有与字符串有关的问题都可以通过它解决<可以了解一下这种数据结构>),但是因为T1有数百万的节点,所以建立suffix tree的时候可能会需要很大的memory space,所以这一种解决方案的缺陷在于空间复杂度比较高。
(2) 另外一种思路就是直接遍历T1,如果遇到T2的root节点则接下来同时遍历T1和T2,如果相同的遍历完成T2,则可以判定T2是T1的子树。时间复杂度最坏为O(n*m),这里n是T1的节点数量,m是T2的节点数量。这里是没有T2的root节点在T1中位置信息,如果有位置信息,能够提供更加准确的时间复杂度。
//copy from cracking the coding interview boolean containsTree(TreeNode t1,TreeNode t2) { if (t2 == null ) return true ; else return subTree(t1,t2); } boolean subTree(TreeNode r1,TreeNode r2) { if (r1 == null ) return false ; if (r1.data == r2.data) if (matchTree(r1,r2)) return true ; return (subTree(r1.left,r2)||subTree(r1.right,r2)); } boolean matchTree(TreeNode r1,TreeNode r2) { if (r2 == null && r1 == null ) return true ; if (r1 == null || r2 == null ) return false ; if (r1.data != r2.data) return false ; return (matchTree(r1.left,r2.left)&&matchTree(r1.right,r2.right)); } |
8.You are given a binary tree in which each node contains a value.Design an algorithm to print all paths which sum up to that value.Note that it can be any path in the tree it does not have to start at the root.
深搜的变种,因为路径可以开始于树种的任意节点。所以算法递归的计算树种的所有节点即可。因为树的节点信息可能是负数,所以这里的路径即使找到了sum总和也不能停止搜索这条路径,因为这条路径的延伸还有可能等于sum。
但是有一个小技巧可能会减少编程复杂度,我们倒过来看,不是输出所有从某个节点出发的路径,而是输出所有截止于该点的等于sum的路径。
//copy from cracking the coding interview void findSum(TreeNode head, int sum,ArrayList<Integer> buffer, int level) { if (head == null ) return ; int tmp = sum; buffer.add(head.data); for ( int i = level;i > - 1 ;i--) { tmp -= buffer.get(i); if (tmp == 0 ) print(buffer,i,level); } ArrayList<Integer> c1 = (ArrayList<Integer>) buffer.clone(); ArrayList<Integer> c2 = (ArrayList<Integer>) buffer.clone(); findSum(head.left,sum,c1,level+ 1 ); findSum(head.right,sum,c2,level+ 1 ); } void print(ArrayList<Integer> buffer, int level, int i2) { for ( int i = level;i <= i2;i++) { System.out.print(buffer.get(i)+ "" ); } System.out.println(); } |
Cracking the Coding Interview(Trees and Graphs)的更多相关文章
- Cracking the Coding Interview(Stacks and Queues)
Cracking the Coding Interview(Stacks and Queues) 1.Describe how you could use a single array to impl ...
- 二刷Cracking the Coding Interview(CC150第五版)
第18章---高度难题 1,-------另类加法.实现加法. 另类加法 参与人数:327时间限制:3秒空间限制:32768K 算法知识视频讲解 题目描述 请编写一个函数,将两个数字相加.不得使用+或 ...
- Cracking the Coding Interview(linked list)
第二章的内容主要是关于链表的一些问题. 基础代码: class LinkNode { public: int linknum; LinkNode *next; int isvisit; protect ...
- Cracking the Coding Interview(String and array)
1.1实现一个算法判断一个字符串是否存在重复字符.如果不能利用另外的数据结构又该如何实现? My solution: /** *利用类似一个hash table的计数 *然后检查这个hash tabl ...
- Cracking the coding interview
写在开头 最近忙于论文的开题等工作,还有阿里的实习笔试,被虐的还行,说还行是因为自己的水平或者说是自己准备的还没有达到他们所需要人才的水平,所以就想找一本面试的书<Cracking the co ...
- Cracking the coding interview 第一章问题及解答
Cracking the coding interview 第一章问题及解答 不管是不是要挪地方,面试题具有很好的联系代码总用,参加新工作的半年里,做的大多是探索性的工作,反而代码写得少了,不高兴,最 ...
- 《Cracking the Coding Interview》读书笔记
<Cracking the Coding Interview>是适合硅谷技术面试的一本面试指南,因为题目分类清晰,风格比较靠谱,所以广受推崇. 以下是我的读书笔记,基本都是每章的课后习题解 ...
- Cracking the coding interview目录及资料收集
前言 <Cracking the coding interview>是一本被许多人极力推荐的程序员面试书籍, 详情可见:http://www.careercup.com/book. 第六版 ...
- CRACKING THE CODING INTERVIEW 笔记(1)
1. Arrays and Strings 1.1 Hash Tables 哈希表,简单的说就是由一个数组和一个hash函数组成实现key/value映射并且能高效的查找的数据结构.最简单的想法就是将 ...
随机推荐
- Oracle免费的便捷Web应用开发框架
Oracle免费的便捷Web应用开发框架 APEX 总体来说,APEX是我见过最便捷最高效的开发框架,用起来比PHP还舒服.上手简单,学习成本极低,曾经有个做行政的小女生,在我指导下两天就可以开发出简 ...
- UVALive 6472 Powers of Pascal
标题手段: 他给了一个无限Pascal阵,定义了powers,然后询问power为P的pascal矩阵里面的第R行C列的元素是多少. 最開始读错题意了...然后 就成了一个神得不得了的题了.后来请教的 ...
- jQuery插件编写及链式编程模型
jQuery插件编写及链式编程模型小结 JQuery极大的提高了我们编写JavaScript的效率,让我们可以愉快的编写代码,做出各种特效.大多数情况下,我们都是使用别人开发的JQuery插件,今天我 ...
- STL 源代码分析 算法 stl_algo.h -- binary_search
本文为senlie原创.转载请保留此地址:http://blog.csdn.net/zhengsenlie binary_search -------------------------------- ...
- signalR例子
不用找了,比较全的signalR例子已经为你准备好了. 这几天想着将一个winform的工具上线到web上,因为对时时性的要求比较高,找朋友咨询了一下推荐了SignlarR 框架,比较强大.昨天才 ...
- DBUtils的使用
DButils是apache旗下Commons项目中的一个JDBC工具包,它可以为帮助我们简化对JDBC的操作,但它并不是一个ORM框架,只是可以为我们执行sql,并将返回的ResultSet转化成我 ...
- 数据库 基于索引的SQL语句优化之降龙十八掌(转)
一篇挺不错的关于SQL语句优化的文章,因不知原始出处,故未作引用说明! 1 前言 客服业务受到SQL语句的影响非常大,在规模比较大的局点,往往因为一个小的SQL语句不够优化,导致数据库性能急 ...
- C# foreach 有用方法具体解释
网上查资料,说foreach 不能改动迭代变量,仅仅能訪问迭代变量.自己理解也不是非常深,通过几个代码进行验证,发现foreach的使用方法还有点特别 验证方法: 1. 迭代变量 为int int[] ...
- 且看三星刚发布的Smart TV如何窃听你的枕边细语
三星最新的SmartTV有一个很酷的新的声控功能,网络连接设备可以通过它来录下你说过的所有内容并把它上传到一个第三方的地方进行存储. 该公司的语音识别软件允许用户跟他们的电视通过声音来进行沟通.一旦电 ...
- C#创建服务及使用程序自动安装服务
.NET创建一个即是可执行程序又是Windows服务的exe 不得不说,.NET中安装服务很麻烦,即要创建Service,又要创建ServiceInstall,最后还要弄一堆命令来安装和卸载. 今天给 ...