前几天看到一个比较老的面试题,想了想还是总结一下记录下来吧,
首先我先定义一下了链表的节点,后面的操作都通用这个类型的节点
struct pNode{
int p_mvalue;
pNode* p_next;
};
一:题目大意是:给两个链表,找出是否相交:
已知的链表的头结点 Head1, Head2
首先说第一种方法,很容易想到,就是两个链表分别从头到尾跑一遍,再看两个节点的指针是否相同,简单直接,但是不能直接返回交点节点的位置;
pNode* tmp1= Head1;
pNode* tmp2= Head2;
while(tmp1->p_next !=NULL )
tmp1= tmp1->p_next;
while(tmp2->p_next !=NULL )
tmp2= tmp2->p_next;
if(tmp1 == tmp2)
cout<<"相交"<<endl;</span>
解法二:就是利用栈,不过这个要求数据比较小,开两个栈分别存放两个链表的节点,首先比两个栈中的顶端节点是否相同,如果相同,然后再逐个弹出,当顶端节点不相同时,那么前一个弹出的节点就是该链表的交点。
stack< pNode* > p1;
stack< pNode* > p2;
pNode* tmp1= Head1;
pNode* tmp2= Head2;
while(tmp1){ //链表一入栈
p1.push(tmp1);
tmp1= tmp1->p_next;
}
while(tmp2 ){//链表二入栈
p2.push(tmp2);
tmp2= tmp2->p_next;
}
if(p1.top() == p2.top()){ //查找交点
while(!p1.empty() && !p2.empty()){
if(p1.top() == p2.top()){
cout<< "相交"<<endl;
break;
}
p1.pop();
p2.pop();
}
}
方法三:这是在知道两个链表的长度的情况下,假设长度分别为 len1, len2 ,再假设len1 <= len2, 那么我们用两个指针tmp1, tmp2 分别指向 Head1, Head2 并且让 tmp2先走(len2 - len1)格,再两个指针同时往后走,直到第一次 tmp == tmp2 或者其中一个指针为 NULL,在这样的情况下,如果链表相交的话,当遇到交点时便利就会结束,效率还是比较可观;
pNode* tmp1= Head1;
pNode* tmp2= Head2;
int dif=len2- len1;
while(dif-- && tmp2 ){
tmp2= tmp2->p_next;
}
while(tmp1 && tmp2){
if(tmp1 == tmp2 ){
cout<< "相交"<<endl;
break;
}
tmp1= tmp1->p_next;
tmp2= tmp2->p_next;
}<
二: 判断单链表中是否有环,很简单,给出两个指针,一个每次跑一格,另一个每次跑两格,如果有环那么一定会相遇的,如果其中一个指针为NULL了 说明跑到表尾了 ,一定没有环的,
三:删除链表中的节点,大意为给定单链表表的头结点Head1 和一个指向链表中一个节点的指针p,删除p所指向的节点。
或许你会想这个简单,一分钟不到就会写下如下代码:
void deletNodep(pNode* Head , pNode* p){
if( Head == NULL || p== NULL )
throw new std::exception("Invalid parameters");
pNode* ptmp=Head;
while( ptmp->p_next != p)
ptmp= ptmp->p_next;
pNode* q=ptmp->p_next;
ptmp->p_next = q->p_next;
delete(q);
}
仔细看看没问题啊,还判断了输入的有效性,但是再想想o(n)的效率还可以接受,那你就错了,其实是有o(1)的解法的:
void deletNodep(pNode* Head , pNode* p){
if( Head == NULL || p== NULL )
throw new std::exception("Invalid parameters");
pNode* q;
if( p->p_next !=NULL ){
q= p->p_next;
p->p_mvalue = p->p_next->p_mvalue;
p->p_next = p->p_next->p_next;
}else{
pNode* ptmp=Head;
while( ptmp->p_next != p)
ptmp= ptmp->p_next;
pNode* q=ptmp->p_next;
ptmp->p_next = q->p_next;
}
delete(q);
}
在输入合法的情况下,如果该节点不是链表的尾节点,那么就将该节点的下一个节点的值付给该节点,把该节点的下一节点删除,时间复杂度 o(1),如果是尾节点,那没办法只有遍历一遍了找到它前面那一节点的next域,再算平均效率 还是o(1),这次便可以接受了。
三、链表的输出操作,顺序输出就不用说了吧,那说说逆序输出吧。
假设链表是没有头结点,首先或许你会想到,我们可以下先把链表反转了,在顺序输出,就方便多了,但是这是最不好的解法,翻转的耗费不说,你改变了链表原有的顺序,
那怎么办那,很快你会想到用栈,正好栈的性质是先进后出,先把所有节点都入栈再逐个弹出,代码也蛮简单:
void printList(pNode *pHead){
if(pHead == NULL )
return ;
stack< pNode* >pStack;
pNode* ptmp=pHead;
while(ptmp){
pStack.push(ptmp);
ptmp= ptmp->p_next;
}
while(!pStack.empty()){
pNode* p;
p=pStack.top();
cout<< p->p_mvalue <<endl;
pStack.pop();
}
}
不过既然想到了栈,那为什么不用递归呢,那样的话代码更加简洁,递归计算基于栈的啊,所以更好的方法应该是这样的:
void printList(pNode *pHead){
if(pHead == NULL )
return ;
printList(pHead->p_next );
cout<< pHead->p_mvalue << endl;
}
哈哈是不是简单多了
四、单链表翻转,
就是已知一单链表的第一个节点的位置,将该链表翻转,并返回翻转后链表的第一个节点的指针
实现这个我们需要借助几个辅助指针依次为,自己拿张纸划一划就知道了是思路很清晰,代码如下:
pNode * revList(pNode *pHead){
if(pHead == NULL )
return NULL;
pNode *p=NULL;
pNode *q=pHead;
pNode *revHead= NULL;
while(q!=NULL){
pNode *r= q->p_next;
q->p_next=p;
if(r== NULL)
revHead= q;
p= q;
q= r;
}
return revHead;
}
还有一写问题,如链表的合并啊,双向链表的操作等等,感觉都不是特别的难,找张纸画画,思路想清楚了,代码都很好实现的。