阿里巴巴面试题之英文语句分割问题

本文探讨了如何高效地对英文文章进行分割,分析了双链表法和hash+单链表法的优缺点,对比了两种方法在不同场景下的性能表现。最后,提出了一种更简单的解决方案,并通过实例解释了其实现原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题大致描述:对英文文章进行分割,其中分割符为‘,’,和‘.' 。分割子句时要记住子句的起初位置和子句长度,并且,将长度相同的子句记录在一起。大概题意只记得这么多了。

面试官给的是双链表法,我当时给的不是这个方法的,后来又写了封信给他了的。以下为信的内容:


尊敬的面试官:


您好,很荣幸能够被您二面,对于您给出的句子分割问题的双链表解法,我个人持有不同观点。如下是我个人的分析,有不妥之处,还请批评指正。


方法一:双链表法


数据结构:


struct LNode


{


    unsigned int index;


    LNode* next;


};


 


struct Adjust


{


    unsigned int len;


    LNode* pNode;


    Adjust* next;


};


 



1 双链表示意图


 


设头链表长度为headLength,i个头结点中pNode所指的链表长度为nodeLenth[i]


插入操作:


  对于长度为length、起始点为index的数据插入链表中,则先遍历头结点链表:


 Adjust* pAdjust = head;


    while (pAdjust != NULL)


    {


       if (pAdjust->len == length)


       {//找到


           LNode* pNode = new LNode; //新建结点


           pNode->index= index;


           pNode->next= pAdjust->pNode;//直接插入其后


           pAdjust->pNode= pNode;


           break;//找到则退出


       }


      


    }


    if (pAdjust == NULL)


    {//没找到则重新重建头结点并插入头链表中


       pAdjust = new Adjust;//创建一个头结点插入头结点链表中,位于head之后


       pAdjust->len= length;


        pAdjust->next  = head;


       head = pAdjust;


       LNode* pNode = new LNode;


       pNode->index= index;


       pNode->next= NULL;


       pAdjust->pNode= pNode;


 


}


复杂度分析:


对于含有headLength个头结点的链表,遍历的最坏时间复杂度为O(headLength),平均值为(headLength+1)/2;


头结点占用的空间为: headLength*sizeof(Adjust);


 


 


查找操作:


在双链表中统计长度为length的句子个数,并且输出每个句子


void queary_length(unsignedint length)


{


   assert(head!= NULL && buff!= NULL);


    Adjust* pAdjust= head;


    while (pAdjust != NULL)


    {//时间复杂度O(headLength)


       if (pAdjust->len == length)


       {//找到


           LNode* pNode = pAdjust->pNode;


           unsigned int  count = 0;//统计个数


           while (pNode != NULL)


           {//时间复杂度O(nodeLength[length])


              count++;


              for (int i=pNode->index; i<pNode->index+length; ++i)


              {//复杂度O(length)


                  cout << buff[i];


              }


           }


           cout<< "长度为"<< length << "的句子总数为:" << count << endl;


          


       }


 


    }//end while


 


} //end func


 


复杂度分析:


对于含有headLength个头结点的链表,统计长度为length的句子的个数并输出它们,


最坏情况下,时间复杂度为:O(headLength*nodeLenth[length]*length)


 


双链表方法适用情况:


    由上面的分析可知,当句子比较少,句子长度的种类N小时,这种方法很优,headLength==N,头节点链表空间N*sizeof(Adjust);都是有效空间,并且不受句子长度的约束。


但是,当句子很多,N很大时,这种方法就不适用了。因为此时N*sizeof(Adjust)将会比N*sizeof(LNode*)大很多,在32位系统中,前者是后者的三倍。详细见方法二


 


方法二:hash+单链表法


数据结构:


#define  MAXLENGTH 600  //最大句子长度


LNode*hash[MAXLENGTH];//hash[i]表示存放长度为i的句子信息的链表的表头


struct LNode


{


    unsigned int index;


    LNode* next;


};



              2 单链表结构示意图


int i = 0;


//数组初始化


for(i=0; i<MAXLENGTH; ++i)


{


    hash[i] = NULL;


}


插入操作:


将长度为length、起始点为index 的数据插入单链表中。


//插入长度为length,起始坐标为index


    if (length >= MAXLENGTH)


    {


       //重新开辟空间


    }


    else


    {


       LNode* pNode = new LNode;//建立新节点并插入


       pNode->index= index;


       pNode->next= hash[length];


       hash[length] = pNode;


    }


 


复杂度分析:


length < MAXLENGTH 时,时间复杂度为O(1),当length>= MAXLENGTH时间复杂度为O(MAXLENGTH);


头结点空间复杂度为O(MAXLENGTH*sizeof(LNode*))


 


查找操作:


   void queary_length(char*buff, unsignedint length)


{


    assert(buff != NULL length < MAXLENGTH);


LNode* pNode = hash[length];


unsigned int count = 0; //统计个数


    while (pNode != NULL)


    {//时间复杂度nodeLength[length]


       count++;


       for (int i=pNode->index; i<pNode->index+length; ++i)


       {//时间复杂度O(length)


           cout << buff[i];


       }


    }


    cout << "长度为"<< length << "的句子总数为:" << count << endl;


 


} //end func


 


复杂度分析:


时间复杂度为O(nodeLenth[length]*length)


 


Hash+单链表法的适用情况:


由于要提前开辟了MAXLENGTHsizeof(LNode*)空间,可见,当句子很少,即句子长度种类N很小时,hash数组中很多头结点为空,即没有被利用上。


但是,当句子很多很多时,此时句子长度的种类N也会很大,此时hash数组中的每个节点都会被利用。只有当length >= MAXLENGTH,才会重新分配空间的。


注意:基于统计学,句子长度length >= MAXLENGTH (MAXLENGTH==600)的语句出现的概率有多大呢?如果,真出现了这样的语句,就重新给hash分配更大的空间,那么在这种情况下,长度在length左右的句子很有可能也会出现。故数组hash的空间不会被浪费。


 


 


两种方法的比较:


由上述可见,当句子长度种类数N很小时,双链表法很适用,而hash+单链表法就额外占用无效空间了,尽管此时查找插入和查找速度都很快。


当处理大量句子,句子长度种类数N很大时(接近但仍然小于MAXLENGHT),双链表法的头结点占用空间为N*sizeof(Adjust),hash+单链表法占用空间为MAXLENGHT*sizeof(LNode*),


此时,前者占用空间约为后者的三倍。也就是说,当N > MAXLENGHT/3时,即N >200时,在空间上,用第二种方法就很划算了。插入时,双链表法的时间复杂度为O(N),而hash+单链表法的时间复杂度为O(1)。查询时,前者的时间复杂度为O(N*nodeLenth[length]*length),而后者的时间复杂度为O(nodeLenth[length]*length),最坏情况下,后者比前者快N倍。


综上可见,此题用hash+单链表法更好。STL中,hashtable用的就是这种结构。


方法三:


   multimap<unsigned int, unsigned int>,以lengthkey,以indexvalue。这样岂不是更简单,只可惜回来路上才想到。


 


 


声明:此信内容仅仅作为我对此问题的二面答案的补充与分析,仅仅是为了技术上的交流,没有其他任何意思,也算是给自己一个交代吧。我一个因为这个被刷就够了,要是别人再因为这个被刷了,那就不好了。



                                         General  Manketon




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值