zoukankan      html  css  js  c++  java
  • [微软面试100题]110

    第一题:把二分查找树转换为升序排序的双向链表。不能新建节点,只能改变树的指针。

    二分查找树:左小于父,右大于父。中序遍历就是升序遍历。
    中序遍历递归法:
    void showMidTree(BSTreeNode *pRoot)
    {
        if(pRoot!=NULL)
        {
            showMidTree(pRoot->m_pLeft);
            cout<<pRoot->m_nValue<<endl;
            showMidTree(pRoot->m_pRight);
        }
    }

    思路一:当我们到达某一结点准备调整以该结点为根结点的子树时,先调整其左子树将左 子树转换成一个排好序的左子链表,再调整其右子树转换右子链表。最近链接左子链表的最右结点(左子树的最大结点)、当前结点和右子链表的最左结点(右子树 的最小结点)。从树的根结点开始递归调整所有结点。

    算法复杂度:中序遍历的复杂度为O(n)
     
    通过新建一个vector再输出值的地方存储找到的指针,这样存储的顺序为升序。然后把指针连接为双向链表。
    struct BSTreeNode{
        BSTreeNode(){
            m_pLeft=NULL;
            m_pRight=NULL;
        }
        bool legal(set<BSTreeNode*> &found);
        int m_nValue;
        BSTreeNode *m_pLeft;
        BSTreeNode *m_pRight;
    };
    
    
     
    //递归建立二分查找树
    BSTreeNode* buildTree(int *num,int count){
        BSTreeNode *pRoot=new BSTreeNode;
        for(int i=0;i<count;++i){
            if(i==0){
                pRoot->m_nValue=num[i];
            }
            else{
                BSTreeNode *pNode=new BSTreeNode;
                BSTreeNode *pTmp=pRoot;
                while(pTmp!=NULL){
                    //比父节点大的分配到右边
                    if(pTmp->m_nValue<=num[i]){
                        if(pTmp->m_pRight==NULL){
                            pTmp->m_pRight=pNode;
                            pTmp->m_pRight->m_nValue=num[i];
                            break;
                        }
                        else{
                            pTmp=pTmp->m_pRight;
                        }
                    }
                    else{
                        if(pTmp->m_pLeft==NULL){
                            pTmp->m_pLeft=pNode;
                            pTmp->m_pLeft->m_nValue=num[i];
                            break;
                        }
                        else{
                            pTmp=pTmp->m_pLeft;
                        }
                    }
                }
            }
        }
        return pRoot;
    }
     
    //中序遍历 就是二分查找树的升序遍历。
    void showMidTree(BSTreeNode *pRoot){
        if(pRoot!=NULL){
            showMidTree(pRoot->m_pLeft);
            cout<<pRoot->m_nValue<<endl;
            showMidTree(pRoot->m_pRight);
        }
    }
     
    void connect(BSTreeNode *pRoot,BSTreeNode* &p){
        if(pRoot!=NULL){
            connect(pRoot->m_pLeft,p);
            //p初值为NULL,之后记录前一个值的指针
            if(p!=NULL){
                pRoot->m_pLeft=p;
                p->m_pRight=pRoot;
                p=pRoot;
            }
            else{
                p=pRoot;
            }
            connect(pRoot->m_pRight,p);
        }
     
    }
     
    BSTreeNode* tree2list(BSTreeNode *pRoot){
        BSTreeNode *p=NULL;
        connect(pRoot,p);
        while(pRoot->m_pLeft!=NULL){
            pRoot=pRoot->m_pLeft;
        }
        return pRoot;
    }
     
    int main()
    {
        int num[]={10,6,14,4,8,12,16};
        BSTreeNode *pRoot=buildTree(num,7);
     
        BSTreeNode *start=tree2list(pRoot);
        while(start!=NULL){
            cout<<start->m_nValue<<endl;
            start=start->m_pRight;
        }
    }
    思路二:我们可以中序遍历整棵树。按照这个方式遍历树,比较小的结点先访问。如果我们每访问一个结点,假设之前访问过的结点已经调整成一个排序双向链表,我们再把调整当前结点的指针将其链接到链表的末尾。当所有结点都访问过之后,整棵树也就转换成一个排序双向链表了。

    貌似答案就是这个思路,还没做出来

     
    第二题:实现一个带功能min的栈。push() pop() min()的时间复杂度都为O(1)
    得到min复杂度为1,需要使用到动态规划的思想。 min Stack[n]=(val[n]>min Stack[n-1] ? min Stack[n-1] : val)
    意思是说,当栈push一个元素的时候,如果新元素大于没push时栈的最小值,则最小值维持不变。否则新的元素为最小值。
    每一个元素用一个结构体记录当时的值,和到当前位置的栈的最小值。这样当pop时就可以马上以O(1)得到pop后新栈的最小值了
    struck minStackElem
    { int val;  int minVal;//从栈底到此元素的最小值};

    第三题:求最大子数组和,使用动态规划复杂度为O(n),题目同编程之美一样

    解法1(不提供最大数组区间,值提供最大值):max初始为0,从头累加数组,如果sum>max则max=sum,通过此方法来记录最大和。因为当遇到负数sum下降时,max是不作记录的。 但当sum下降到负数时,最大和就为0(区间大小为0,不作区间和已经为0已经为最大了),负数肯定比0小,所以已经肯定不是最大和了,因此赋值sum=0。
    解法2(编程之美解法,详细见另外的日志):把数组分割为前半部分和最后一个元素,数组的最大和分为3种情况,1是最大和区间存在于前半段(不包括最后一个元素的段),2是最大和从最后一个元素开始,3是最大和从最后一个元素之后的元素开始。

    第四题:二元树中找出和为某一值的所有路径

    采用递归的思想,函数递归调用子节点,并把目标数值改为原始目标-本节点值作为新目标。递归函数还有一个队列参数(非引用),相当于每次递归调用都复制一次队列,这样就确保每一条路径有一个队列来保存,不会混乱。每次经过节点就把节点值打入队列。如发现函数的目标==当前节点值,则说明已经找到一条路径。打印路径的方法是把队列的元素依次打印即可。
    void BinaryTreeNode::getPath(int thisSum,int sum,deque<int> deq){
        deq.push_back(this->m_nValue);
        //find it
        if(thisSum==this->m_nValue){
            cout<<"find:"<<endl;
            while(deq.size()>0){
                cout<<deq.front()<<endl;
                deq.pop_front(); }
            return; }
    if(this->m_nValue<thisSum){
            if(this->m_pLeft!=NULL){
                this->m_pLeft->getPath(thisSum-this->m_nValue,sum,deq);
            }
            if(this->m_pRight!=NULL){
                this->m_pRight->getPath(thisSum-this->m_nValue,sum,deq);
            }
    }
    }

    第五题:寻找数组中最小的K个元素

    采用局部堆排序,时间复杂度为O(nlogk)。
    堆:父节点总比子节点要大(小),插入数据后调整堆的复杂度为logk。

    最大堆原理解释:http://blog.csdn.net/xiaoxiaoxuewen/article/details/7570621

     第七题:判断单链表是否相交,是否有环
    判断有环:指针slow步进为1,指针fast步进为2,如果链表有环,则两指针将会在环内相遇。
    方法一(链表有无环都适用):把链表1的节点地址存入hashtable,遍历链表2判断地址是否在hashtable中。时间复杂度为O(n1+n2)
    方法二:(无环适用):如两单链表相交,则从交点到最后的节点都是重复的。因此找到两链表的最后节点对比即可。复杂度同上。
    方法三:(适用有环):链表一步长为1前进,链表二步长2前进,如两链表相交则必然相遇。(因为会汇集到同一环上)。

    于此,还得好好总结下,问题得这样解决:

    1.先判断带不带环

    2.如果都不带环,就判断尾节点是否相等

    3.如果都带环,那么一个指针步长为1遍历一链表,另一指针,步长为2,遍历另一个链表。

    但第3点,如果带环 但不相交,那么程序会陷入死循环。。

    第八题:颠倒单向链表顺序

    单向链表建立与释放:
    //在析构函数中调用下个个节点的delete就可以只在外层delete头节点就可以连锁释放所有节点了。
    template<class T>
    link<T>::~link(){
        if(this->next!=NULL){
            delete this->next;
        }
        cout<<this->val<<" has been released"<<endl;
    }
     
    template<class T>
    void link<T>::append(T val){
        link<T> *tmp=this;
        while(tmp->next!=NULL){
            tmp=tmp->next;
        }
        tmp->next=new link<T>;
        tmp->next->val=val;
        cout<<tmp->next->val<<" is appended"<<endl;
    }

    递归方法:

    //技巧:使用参数传递所需的前值(以前我一般喜欢用返回值),
    //使用返回值传递结果(结果通常在最底层),如现在的反向链表的头指针
    template<class T>
    link<T>* link<T>::reverse(link<T> *rnext){
        if(this->next!=NULL){
            link<T>* head=this->next->reverse(this);
            this->next=rnext;
            return head;
        }
        this->next=rnext;
        return this;
    }

    非递归方法:

    template<class T>
    link<T>* link<T>::reverseUnrecursive(){
        link<T> *previous=NULL,*head=this,*pNext=this->next;
        while(head!=NULL){
            pNext=head->next;
            head->next=previous;
            previous=head;
            head=pNext;
        }
        return previous;
    }

    第八题:通用字符串匹配-非递归法-可输出第一个匹配的子串

    ?代表1一个字符,*代表0个或任意个,其他字符严格匹配
    方法:对格式串从头遍历,根据格式串的字符分情况讨论。1、当为? 2、当为* 3、当为普通字符 4、当匹配失败
    输出匹配段方法:根据match函数返回的从第iMatch个字符开始匹配,根据模式打印出来,详细见代码。
    开发方法:先写测试函数,包含多个测试用例覆盖所有边界条件。每次修改都运行测试函数确保满足所有情况。
     
    第八题:通用字符串匹配-递归法-只能判断是否完全匹配。
    比如模式是a?,它不跟abc完全匹配,只跟ab完全匹配,但用上面的方法就会返回iMatch=0,因为abc中的子串ab与a?匹配。

    跟上面一样分4种情况进行递归即可,详细看源码。我做的方法与答案的不同,但更好理解。采用先编写测试用例再编程确实非常高效!(回归测试

     第八题:翻转字符串
    方法:从头和从尾同时向中间逼近,每走一步调换头尾字符即可。复杂度仅为O(n/2)。PS:char *p="abc",p是const不能改变字符串内容

    第八题:翻转句子的词

    方法:先用上面的翻转字符串的函数把整个句子反转了。然后再用上面的函数逐个单词反转。为了能把上面函数使用到单词上,函数需要传递一个参数,单词或字符串的长度,而不是像平常一样用\0来判断字符串结束。这样就可以运用到单词上了。

    第八题:查找子字符串

    方法1:遍历字符串,如其中字母与子字符串的首字母相同,则再判断后来的是否与子串完全相同,是则找到。不是则继续往下遍历。
    方法2:把字符串建立字典树。然后在字典树中查找子串对应的分支是否有与子串一样的。复杂度为O(k),k为子串长度。

    第八题:比较字符串,要求O(n)时间复杂度和O(1)空间复杂度

    一一对比,不符则跳出返回即可。注意编程简洁风格。单行的if应直接写后面不写括号

    第八题:一个长为1001的数组中乱序存放1到1000的整数,除一个数外其余都不重复。只可以遍历数组一次且不可使用辅助存储,找出重复。

    方法1:数组和-sum(1,1000)=重复数
    方法2:利用 A^A^B=B的性质。将k初始为0,k^=(1到100)再^=(数组各个元素)。由于这样1~1000每个都异或了2次相互抵消了,剩下的就是异或了3次的重复数。
    int a[]={1,5,2,3,4,5};
        int k=0;
        for(int i=0;i<6;++i){
            k^=a[i];
            if(i<5)k^=i+1;
        }
        cout<<k<<endl;

    异或的性质:同则0- A^A=0 不同则1- A^B=1;异或0不影响A^0=A;因此初始化为0.

    注意:异或^   按位取反~,   k与b取异或 k^=b     k取反~k

    第八题:不用乘法和加法,运算一个整数的八倍,七倍

    左移一位相当于乘以2,八倍:k<<3  七倍:(k<<3)-k
    注意:无论左移右移,移动的位数都放在右操作数

    第九题:判断整数序列是否是二元查找树后序遍历的结果

    序遍历:-左-右 访问中,再访问左子树,最后右子树。
    序遍历:左-右-
    序遍历:左--右

    二元查找树中序:从小到大; 后序:最后的元素为中间值,前半段元素小于中间值,后半段大于中间值

    利用二元查找树后序的性质,可根据数组的下标把数组分段,然后使用递归。
    int isPostOrder(int *a,int start,int end){
        if(start==end)return 1;
        int pivot=a[end];//最后的元素是树根,前半段小于根,后半段大于根
        int mid=(end+start)/2;
        int i,j;
        for(i=start;i<mid;++i){
            if(a[i]>pivot)return 0;
        }
        for(j=end-1;j>=mid;--j){
            if(a[j]<pivot)return 0;
        }
        if(i!=j+1)return 0;//判断是否对称,是否二元树
        return isPostOrder(a,start,mid-1) && isPostOrder(a,mid,end-1);
    }
  • 相关阅读:
    Windows内核对象
    FreeWriting_1
    FreeWriting_2
    【整理】技术文章集锦
    【转】英语吵架一百句
    像 IDE 一样使用 vim
    统治世界的十大算法
    AnimationSet动画集合类的使用
    帮你解答adb是什么,adb有什么用
    SharedPreferences的简单使用
  • 原文地址:https://www.cnblogs.com/iyjhabc/p/2986067.html
Copyright © 2011-2022 走看看