链表面试题总结(一)

链表是面试最常问到的代码,虽然每个代码不长,但是逻辑能力和代码能力却能很好的体现出来。
我们就来看一下链表面试题都有什么吧!
1. 比较顺序表和链表的优缺点,说说它们分别在什么场景下使用?
2. 从尾到头打印单链表
3. 删除一个无头单链表的非尾节点(不能遍历链表)
4. 在无头单链表的一个非头节点前插入一个节点
5. 单链表实现约瑟夫环(JosephCircle)
6. 逆置/反转单链表
7. 单链表排序(冒泡排序&快速排序)
8. 合并两个有序链表,合并后依然有序
9. 查找单链表的中间节点,要求只能遍历一次链表
10. 查找单链表的倒数第k个节点,要求只能遍历一次链表
11. 删除链表的倒数第K个结点
12. 判断单链表是否带环?若带环,求环的长度?求环的入口点?并计算每个算法的时间复
杂度&空间复杂度。
13. 判断两个链表是否相交,若相交,求交点。(假设链表不带环)
14. 判断两个链表是否相交,若相交,求交点。(假设链表可能带环)【升级版】
15. 复杂链表的复制。一个链表的每个节点,有一个指向next指针指向下一个节点,还有一
个random指针指向这个链表中的一个随机节点或者NULL,现在要求实现复制这个链表,
返回复制后的新链表。
16. 求两个已排序单链表中相同的数据。void UnionSet(Node* l1, Node* l2);
解决方法
(1)比较顺序表和链表的优缺点,说说它们分别在什么场景下使用?

  1. 从结构来看:顺序表的存储空间是连续的,在内存中需要一块完整的空间来构造顺序表;链表的存储空间在逻辑上是连续的,但是在物理地址上可以连续也可以不连续,在内存中比较好构造空间,也较好的利用零碎空间,但是如果不注意,会引起内存碎片的问题。
  2. 从功能上看:顺序表支持随机访问,在访问某一元素时效率较高,适合查找和随机存取操作。顺序表的插入和删除操作来讲,需要移动元素,时间复杂度为O(n),不适合进行插入和删除操作。链表不支持随机访问操作,链表的插入和删除操作不需要移动结点,直接插入即可时间复杂度为O(1),效率较高。
  3. 从CPU的缓存效率来看:顺序表的CPU高速缓存效率较高(因为空间连续),链表的CPU高速缓存效率较低(空间物理地址不连续)。
    在看其他题目之前,先看一下链表的结点结构和一些插入删除操作
typedef struct ListNode
   {
      int  data;
      ListNode* pNext;

   }Node,*pNode;
void pushBack(ListNode*& phead,int data)
 {
     Node* pcur=phead;
     Node* newNode =new ListNode();
     newNode->data=data;
     if(phead==NULL)
       {
           phead=newNode;
           phead->pNext=NULL;
       }
     else
     {
        while(pcur->pNext!=NULL)
       {
          pcur=pcur->pNext;
       }
        pcur->pNext=newNode;
        newNode->pNext=NULL;
     }
 }
    void pushHead(ListNode*& phead,int data)
 {
     Node* newNode=new ListNode();
     newNode->data=data;
     newNode->pNext=phead;
     phead=newNode;
 }
void popBack(ListNode*&phead)
 {
     if(phead==NULL)
         return;
     if(phead->pNext==NULL)
     {
         delete phead;
         phead=NULL;
     }
     ListNode* pDel=phead;
     while(pDel->pNext->pNext!=NULL)
     {
         pDel=pDel->pNext;
     }
     delete pDel->pNext;
     pDel->pNext=NULL;

 }
void popHead(ListNode*&phead)
 {
     if(phead==NULL)
         return;
     ListNode* pDel=phead;
     phead=pDel->pNext;
     delete pDel;
     pDel=NULL;
 }
 void InitList(ListNode*&phead)
 {
     phead=NULL;
 }

(2)可用递归方式或者借助栈
递归方式

 void PrintListReverseRecur(ListNode* phead)
  {
      if(phead!=NULL)
      {
          if(phead->pNext!=NULL)
          PrintListReverseRecur(phead->pNext);
          cout<<phead->data;
      }
  }

栈方式

void PrintListReverse(ListNode* phead)
  {
      stack<pNode> s;
      if(phead==NULL)
          return;
      pNode pcur=phead;
      while(pcur!=NULL)
      {
          s.push(pcur);
          pcur=pcur->pNext;
      }
      while(!s.empty())
      {
          pNode  print=s.top();
          cout<<print->data;
          s.pop();
      }
  }

(3)用后一个结点的值代替当前结点的值,删除后一个结点

     void DelNoHeadTailNode(ListNode* phead,ListNode* pcur)
 {
     if(phead==NULL||phead->pNext==NULL)
          return;
     ListNode*pDel=pcur->next;
     pcur->data=pDel->data;
      pcur->pNext=pDel->pNext;
    delete pDel;
  }

(4)先在当前结点的后面插入一个新结点,用新结点的值代替当前结点,用当前结点的值替换新结点的值

void InsertNoheadFront(ListNode*pos,int data)
  {
      assert(pos);
      ListNode* newNode=new ListNode;
    newNode->pNext=pos->pNext;
    pos->pNext=newNode;
    newNode->data=pos->data;
      pos->data=data;
  }

(5)实现约瑟夫环

int LastRemaining(size_t n,size_t m)
 {
     if(n<1||m<1)
       return -1;
    size_t i=0;
    list<int> nums;
    for(i=0;i<n;i++)
    {
         nums.push_back(i);
     }

     list<int>::iterator current=nums.begin();
     while(nums.size()>1)
     {
         for(int i=1;i<m;i++)
        {
            current++;
             if(current==nums.end())
                 current=nums.begin();   
         }
         list<int>::iterator next=++current;
         if(next==nums.end())
             next=nums.begin();

        --current;
         nums.erase(current);
         current=next;
     }
     return (*current);
 }
int LastReNum(size_t n,size_t m)
 {
    if(n<1||m<1)
         return -1;
    int last=0;
     for(int i=2;i<=n;i++)
     {
         last=(last+m)%i;
     }
     return last;
 }

(6)三指针法实现链表逆置

    ListNode* ReverseList(ListNode* phead)
  {
    if(phead==NULL||phead->pNext==NULL)
        return;
     ListNode* pre=NULL;
      ListNode* pcur=phead;
      ListNode* pnext=NULL;
     ListNode* ReverHead=NULL;
      while(pcur!=NULL)
      {
          pnext=pcur->pNext;
           if(pnext==NULL)
               ReverHead=pcur;
       pcur->pNext=pre;
         pre=pcur;
         pcur=pnext;
     }
     return ReverHead;
 }

(7)冒泡排序单链表

 void BubbleSortList(ListNode* phead)
361 {
362     if(phead==NULL||phead->pNext==NULL)
363         return;
364     ListNode* pcur=phead;
365     ListNode* pnext=NULL;
366     bool flag=false;
367     while(pcur->pNext!=NULL)
368     {
369         ListNode* pagain=pcur;
370         for(pnext=pagain->pNext;pnext!=NULL;pnext=pnext->pNext)
371         {
372             int temp=pagain->data;
373             if(temp>pnext->data)
374             {
375                 pagain->data=pnext->data;
376                 pnext->data=temp;
377                 flag=true;
378                 pagain=pnext;
379                                                                             
380             }
381             if(flag==false)
382                 return head;
          }
384         pcur=pcur->pNext;
385     }
386 }

(8)递归合并,大事化小

 ListNode* MergeList(ListNode* phead1,ListNode*phead2)
 76 {
 77     if(phead1==NULL)
 78         return phead2;
 79     else if(phead2==NULL)
 80         return phead1;
 81 
 82     ListNode* MergeNode=NULL;
 83     ListNode* pcur1=phead1;
 84     ListNode* pcur2=phead2;
 85     if(pcur1->data<pcur2->data)
 86     {
 87         MergeNode=pcur1;
 88         MergeNode->pNext=MergeList(pcur1->pNext,pcur2);
 89     }
 90     else
 91     {
 92         MergeNode=pcur2;
 93         MergeNode->pNext=MergeList(pcur1,pcur2->pNext);
 94     }
 95     return MergeNode;
 96 }

(9)快慢指针法查找中间结点

 ListNode* MiddleNode(ListNode* phead)
 {
     ListNode* pFast=phead;
     ListNode* pSlow=phead;
     while(pFast!=NULL&&pFast->pNext!=NULL)
     {
         pFast=pFast->pNext-pNext;
        pSlow=pSlow->pNext;
     }
     return pSlow;
 }

(10)先后两个指针法查找倒数第K个结点

 ListNode* FindKthToTail(ListNode* phead,size_t k)
109 {
110     if(phead==NULL||k==0)
111         return NULL;
112 
113     ListNode* pAhead=phead;
114     ListNode* pBehind=phead;
115     for(size_t i=0;i<k-1;i++)
116     {
117         if(pAhead->pNext!=NULL)
118              pAhead=pAhead->pNext;
119         else
120             return NULL;
121     }
122     while(pAhead->pNext!=NULL)
123     {
124         pAhead=pAhead->pNext;
125         pBehind=pBehind->pNext;
126     }
127     return pBehind;
128 }

(10) 删除倒数第K个结点:两指针法,第一个指针先走K步,然后两个指针一起走,第二个指针的next就是要删除的指针。

void DelKthtoTail(ListNode* phead,size_t k)
130 {
131     if(phead==NULL||k==0)
132         return;
133     ListNode* pFirst=phead;
134     ListNode* pSecond=phead;
135     for(size_t i=0;i<k;i++)
136     {
137         if(pFirst->pNext!=NULL)
138             pFirst=pFirst->pNext;
139         else
140             return NULL;
141     }
142     while(pFirst->pNext!=NULL)
143     {
144         pFirst=pFirst->pNext;
145         pSecond=pSecond->pNext;
146     }
147     ListNode* pDel=pSecond->pNext;
148     pSecond->pNext=pDel->pNext;
149     delete pDel;
150     
151 }                          

(11)快慢指针法,快指针每次走两步,慢指针每次走一步,如果带环,一定会相遇,返回相遇结点;如果不带环,则快指针走到NULL位置结束。

 ListNode* MeetingNode(ListNode* phead)
153 {
154     if(phead==NULL)
155         return;
156     ListNode* fast=phead;
157     ListNode* slow=phead;
158     while(fast!=NULL&&slow!=NULL)
159     {
160        if(fast==slow)
161            return fast;
162        slow=slow->pNext;
163        fast=fast->pNext;
164 
165        if(fast!=NULL)
166            fast=fast->pNext;
167     }
168     return NULL;
169 }

如果带环,利用相遇结点来求出环中结点的个数

int GetCicleLen(ListNode* phead)
{
 ListNode* meetingNode=MeetingNode(phead);
173     if(meetingNode==NULL)
174         return NULL;
175     //Nodes of num
176     int nodesLoop=1;
177     ListNode* pcur=meetingNode;
178     while(pcur->pNext!=meetingNode)
179     {
180         pcur=pcur->pNext;
181         ++nodesLoop;
182     }
    return nodesLoop;
}

如果带环,先求出环中结点的个数,让第一个指针先走nodesLoop步,然后两指针一起走,直到相遇,就是环的入口点。

ListNode* EntryNode(ListNode* phead)
171 {
172     ListNode* meetingNode=MeetingNode(phead);
173     if(meetingNode==NULL)
174         return NULL;
175     //Nodes of num
176     int nodesLoop=1;
177     ListNode* pcur=meetingNode;
178     while(pcur->pNext!=meetingNode)
179     {
180         pcur=pcur->pNext;
181         ++nodesLoop;
182     }
183    //entry
184    pcur=phead;
185    for(int i=0;i<nodesLoop;i++)
186    {
187        pcur=pcur->pNext;
188    }
189    ListNode* pcur2=phead;
190    while(pcur!=pcur2)
191    {
           pcur=pcur->pNext;
193        pcur2=pcur2->pNext;
194    }
195    return pcur;
196 }

(13)不带环:如果两个链表相交,则最后一个结点一定是一样的。

    bool Isintersect(ListNode* phead1,ListNode* phead2)
210 {
211     if(phead1==NULL||phead2==NULL)
212         return false;
213     while(phead1->pNext!=NULL)
214     {
215         phead1=phead1->pNext;
216     }
217     while(phead2->pNext!=NULL)
218     {
219         phead2=phead2->pNext;
220     }
221     if(phead1==phead2)
222         return true;
223     else
224         return false;
225 
226 }

如果相交,则第一个交点一定是:求出两个链表的长度,求出两长度之差Div,让长链表先走Div步,然后两指针一起走,直到相遇。

 int GetListLength(ListNode* phead)
198 {
199     if(phead==NULL)
200         return 0;
201     int length=0;
202     while(phead!=NULL)
203     {
204         length++;
205         phead=phead->pNext;
206     }
207    return length; 
208 }
ListNode* FindFirstNode(ListNode* phead1,ListNode* phead2)
292 {
293     if(Isintersect(phead1,phead2))
294    {
295     int length1=GetListLength(phead1);
296     int length2=GetListLength(phead2);
297 
298     int Dif=length1-length2;
299     ListNode* Long=phead1;
300     ListNode* Short=phead2;
301     if(length2>length1)
302     {
303         Dif=length2-length1;
304         ListNode* Long=phead2;
305         ListNode* Short=phead1;
306     }
307     for(int i=0;i<Dif;i++)
308         Long=Long->pNext;
309     while(Long!=NULL&&Short!=NULL&&Long!=Short)
310     {       
                 Long=Long->pNext;
312             Short=Short->pNext;
313     }
314     ListNode* pFirstCommonNode=Long;
315     return pFirstCommonNode;
316   }
317 }

(14)两链表可能带环:最终归为只有两个链表同时带环时才可能相交

     bool IsintersectCross(ListNode* phead1,ListNode* phead2)
228 {
229     ListNode* meetNode1=MeetingNode(phead1);  //判断是否带环
230     ListNode* meetNode2=MeetingNode(phead2);
231     if(meetNode1==NULL&&meetNode2==NULL)
232         return Isintersect(phead1,phead2); //如果两个都不带环则是上一个题的判断方式
233     else if(meetNode1&&meetNode2)  //如果两个都带环,判断是否相交
234     {
235         int n=GetCircleLen(phead1); //求环的长度
236         while(n--)
237         {
238             if(meetNode1==meetNode2)
239                 return true;
240 
241             meetNode1=meetNode1->pNext;
242         }

243         return false; 
244     }
245     else             //一个带环一个不带环不相交
246         return false; 
247 }

如果两带环链表相交,则分别求出两链表的入口点,求出头到入口点的距离,求出差的距离。让长链表先走差距离步,然后两个链表一起走,直到相遇。

ListNode* MeetingNodeCircle(ListNode* phead1,ListNode* phead2)
249 {
250     ListNode* pcur1=EntryNode(phead1);
251     ListNode* pcur2=EntryNode(phead2);
252     ListNode* pH1=phead1;
253     ListNode* pH2=phead2;
254     int len1=1;
255     int len2=1;
256     int SubLen=0;
257     if(pcur1!=pcur2)
258         return pcur1;
259     while(pH1!=pcur1)
260     {
261         len1++;
262         pH1=pH1->pNext;
263     }
264     while(pH2!=pcur2)
265     {
266         len2++;
267         pH2=pH2->pNext;
268     }   

273     SubLen=len1-len2;
274     ListNode* pLong=phead1;
275     ListNode* pShort=phead2;
276     if(len1<len2)
277     {
278         SubLen=len2-len1;
279         ListNode* pLong=phead2;
280         ListNode* pShort=phead1;
281     }
282     for(int i=0;i<SubLen;i++)
283         pLong=pLong->pNext;
284     while(pLong!=NULL&&pShort!=NULL&&pLong!=pShort)
285     {
286         pLong=pLong->pNext;
287         pShort=pShort->pNext;
288     }
289     return pLong;                                                           

(15) 复杂链表的复制
复杂链表的复制
(16)对于两个链表,分别从头开始比较,如果第一个链表的数据大于第二个链表,则第二个链表向后走;反之,则第一个链表向后走,如果相等,则开辟新结点保存

ListNode UnionSet(ListNode* pL1, ListNode* pL2)
{
    ListNode* PSList1 = pL1;
    ListNode* PSList2 = pL2;
    ListNode* pNewHead = NULL;
    ListNode* pNode = NULL;
    //每次比较两个链表头指针指向的数据是否相等,不相等,就让数据小的头指针后移,相等,则把该数据保存起来,
    //两个头指针同时后移,直到其中一个指向空为止
    while ((NULL= PSList1) || (NULL= PSList2))
    {
        if (PSList1->data > PSList2->data)
        {
            PSList2 = PSList2->pNextNode;
        }
        else if (PSList1->data < PSList2->data)
        {
            PSList1 = PSList1->pNextNode;
        }
        //用一个新的链表来保存两个链表中相同的数据
        else
        {
            ListNode* newNode=new ListNode;
            newNode->data=PSList1->data;
            newNode->pNext=pNode;
            pNode=newNode;
        }
    }
    return pNode;
}

猜你喜欢

转载自blog.csdn.net/qq_37954088/article/details/81411076