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)的更多相关文章

  1. 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 ...

  2. 二刷Cracking the Coding Interview(CC150第五版)

    第18章---高度难题 1,-------另类加法.实现加法. 另类加法 参与人数:327时间限制:3秒空间限制:32768K 算法知识视频讲解 题目描述 请编写一个函数,将两个数字相加.不得使用+或 ...

  3. Cracking the Coding Interview(linked list)

    第二章的内容主要是关于链表的一些问题. 基础代码: class LinkNode { public: int linknum; LinkNode *next; int isvisit; protect ...

  4. Cracking the Coding Interview(String and array)

    1.1实现一个算法判断一个字符串是否存在重复字符.如果不能利用另外的数据结构又该如何实现? My solution: /** *利用类似一个hash table的计数 *然后检查这个hash tabl ...

  5. Cracking the coding interview

    写在开头 最近忙于论文的开题等工作,还有阿里的实习笔试,被虐的还行,说还行是因为自己的水平或者说是自己准备的还没有达到他们所需要人才的水平,所以就想找一本面试的书<Cracking the co ...

  6. Cracking the coding interview 第一章问题及解答

    Cracking the coding interview 第一章问题及解答 不管是不是要挪地方,面试题具有很好的联系代码总用,参加新工作的半年里,做的大多是探索性的工作,反而代码写得少了,不高兴,最 ...

  7. 《Cracking the Coding Interview》读书笔记

    <Cracking the Coding Interview>是适合硅谷技术面试的一本面试指南,因为题目分类清晰,风格比较靠谱,所以广受推崇. 以下是我的读书笔记,基本都是每章的课后习题解 ...

  8. Cracking the coding interview目录及资料收集

    前言 <Cracking the coding interview>是一本被许多人极力推荐的程序员面试书籍, 详情可见:http://www.careercup.com/book. 第六版 ...

  9. CRACKING THE CODING INTERVIEW 笔记(1)

    1. Arrays and Strings 1.1 Hash Tables 哈希表,简单的说就是由一个数组和一个hash函数组成实现key/value映射并且能高效的查找的数据结构.最简单的想法就是将 ...

随机推荐

  1. 纠错《COM技术内幕》之ProgID

    近期在看<COM技术内幕>,看到第六章时发现该章节在解释ProgID时有点错误,特此记录一下,也给正在学习COM的小伙伴们一个提示. 并且我发现该问题存在于一些非常多大型软件的COM组件中 ...

  2. VC各种方法获得的窗口句柄

    AfxGetMainWnd AfxGetMainWnd获取窗口句柄本身 HWND hWnd = AfxGetMainWnd()->m_hWnd; GetTopWindow 功能:子窗体z序(Z序 ...

  3. Eclipse部署Web项目(图文讲解)

    讲解是在linux下完成的,但对windows系统,操作也是一样的,不要被吓到了 1.下载Eclipse

  4. 各种加密解密函数(URL加密解密、sha1加密解密、des加密解密)

    原文:各种加密解密函数(URL加密解密.sha1加密解密.des加密解密) 普通hash函数如md5.sha1.base64等都是不可逆函数.虽然我们利用php可以利用这些函数写出可逆函数来.但是跨语 ...

  5. PHP+百度地图API+JAVASCRIPT实现GPS坐标与百度坐标转换的实例

    原文:PHP+百度地图API+JAVASCRIPT实现GPS坐标与百度坐标转换的实例 <!--小幅的坐标转换点位程序--> <!DOCTYPE html> <html&g ...

  6. 【百度地图API】自定义可编辑的交通路线

    原文:[百度地图API]自定义可编辑的交通路线 任务描述: 我想自己绘制一条从地铁站出口到天安门的道路,而且还需要根据我的喜好来改变这条路线. 如何实现: 鼠标左击地图,绘制路线:双击后,绘制结束:绘 ...

  7. vim插件管理器vundle

    安装:  git clone http://github.com/gmarik/vundle.git ~/.vim/bundle/vundle set nocompatible " be i ...

  8. 多线程学习之五超时模式Timer

    timed[超时模式]案例:一个线程提供下载数据,另一个线程执行下载,如果有5秒钟以上,提供下载的线程没有提供数据,下载线程因超时异常,停止下载线程运行. 超时异常类 /** * */ package ...

  9. c语言发挥帕斯卡三角

    我们已经确定了帕斯卡三角的规则,下面是我的代码,非常实用哦! !! #include<stdio.h>  void main()  {      int i,j,n,k;      sca ...

  10. 快速构建Windows 8风格应用35-触控输入

    原文:快速构建Windows 8风格应用35-触控输入 引用 Windows 8设备通常具有多点触摸屏,用户可以同时使用多个手指来进行不同的输入交互,如点击.拖动或收缩等手势操作.另外Windows ...