收集一些关于链表的常见面试笔试题。

链表结构:

typedef struct ListNode
{
    int val;
    ListNode* next;        
}ListNode;

1. 链表反转

思路:将需要反转的结点的下一个结点暂存起来,然后将需要反转的结点与它指向的下一个结点交换指针位置,交换完毕以后,再将指针指向暂存的结点即可,这样相当于是改变了链表指针的指向,同时要注意到,返回值是原先链表的链表尾,可以通过判断下一结点是否为空,如果为空则说明到达链表末尾,此时就可以将结果返回,链表已经被反转成功。

ListNode* reverseList(ListNode* pHead)
{
    if (pHead == NULL || pHead->next == NULL) //链表为空或者仅1个数直接返回
        return pHead;
    ListNode* p = pHead, *res= NULL; //定义一个节点,保存最终结果
    ListNode* temp;
    while (p != NULL)                  //循环到链尾
    {
        temp = p->next;      //暂存p下一个地址,防止变化指针指向后找不到后续的数
        p->next = res;                 //p->next指向前一个空间
        res     = p;                       //新链表的头移动到p,扩长一步链表
        p       = temp;                  //p指向原始链表p指向的下一个空间
    }
    return res;
}

 

2. 从尾到头输出单链表

思路:

1)从头到尾遍历链表,每经过一个节点的时候,把该节点放到栈中。当遍历完整个链表后,再从栈顶开始输出结点的值,此时输出的节点的顺序已经反转过来了。该方法需要维护一个额外的栈空间;

2)既然想到了栈来实现这个函数,而递归本质上就是一个栈结构,于是可以用递归来实现。要实现反过来输出链表,每访问一个节点的时候,先递归输出到它后面的节点,再输出该节点自身,这样即链表的输出结果就反过来了。

思路1实现:

void PrintListReverse(ListNode *pHead)
{
    stack<ListNode*> s_node;
    ListNode* tmp = pHead;   
    while(temp != NULL)
    { 
         s_node.push(temp);
         temp = temp->next;  
    }

    while(!s_node.empty())
    {
         printf("%d",s_node.top()->val);
         s_node.pop();  
    }
}

思路2:

void PrintListReverse(ListNode *pHead)
{
    if(pHead != NULL)
    { 
         PrintListReverse(pHead->next);   //先递归输出到它后面的节点
    }
    printf("%d", pHead->val);        //输出后一个节点自身
}

此题还有两个常见的变体:
(1)从尾到头输出一个字符串;

(2)定义一个函数求字符串的长度,要求该函数体内不能声明任何变量;

对于这两个变体,都可以参考思路2的实现方式。

 

3. 合并两个有序链表

思路:逐个比较两个链表的元素,较小的元素进入到新的链表中,并且指向它的下一个。递归法实现。

ListNode* Merge(ListNode* n1, ListNode* n2)
{
    if (n1 == NULL) {
        return n2;
    } else if (n2 == NULL) {
        return n1;
    }

    ListNode* res = NULL;
    if (n1->val < n2->val)
    {
        res = n1;
        res->next = Merge(n1->next, n2);
    } else {
        res = n2;
        res->next = Merge(n1, n2->next);
    }
    return res;
}

 

4. 判断单链表是否有环

此题有很多种方法,这里介绍一种比较巧妙的方法,使用STL中的map实现。

思路:首先定义 map<ListNode*,int> m; 将一个ListNode*指针映射成数组的下标,并赋值为一个int类型的数值。然后从链表的头指针开始往后遍历,每次遇到一个指针p,就判断m[p]是否为0。如果为0,则将m[p]赋值为1,表示该节点是第一次访问;而如果m[p]的值为1,则说明这个节点已经被访问过一次了,于是就形成了环。

map<ListNode*, int> m;
bool IsLoop(ListNode *phead)
{
    if(pHead == NULL)
        return  false;
    ListNode* p = pHead;
    while(p != NULL)
    {
        if(m[p] == 0) 
            m[p] = 1;
        else if(m[p] == 1) 
            return true;
        p = p->next;
    }
}

延伸:找到有环链表的环入口

思路:

//断链法:破坏链表结构
/*
时间复杂度为O(n),两个指针,一个在前面(front),另一个(pre)紧邻着这个指针,在后面。
两个指针同时向前移动,每移动一次,令前面的指针的next指向NULL。
也就是说:访问过的节点都断开,最后到达的那个节点一定是尾节点的下一个,
也就是循环的第一个。
这时候已经是第二次访问循环的第一节点了,第一次访问的时候我们已经让它指向了NULL,
所以到这结束。
*/

ListNode* EntryNodeOfLoop(ListNode* pHead)
{
    if(!pHead->next)
        return NULL;
    ListNode* pre = pHead;
    ListNode* front = pHead->next;
    while(front){
        pre->next = NULL;
        pre=front;
        front = front->next;
    }
    return pre;
}

 

5. 找到链表的倒数第k个节点

思路:创建两个指针,先让第一个指针和第二个指针都指向头结点,然后再让第一个指针走(k-1)步,到达第k个节点,然后两个指针同时往后移动,当第一个结点到达末尾的时候,第二个结点所在位置就是倒数第k个节点了

ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
  ListNode *pHead = pListHead;
  ListNode *pTail = pListHead;
  if(pListHead==NULL || k==0)
    return NULL;
  for(int i=1;i<k;++i){
    if(pTail->next != NULL)
    {
      pTail=pTail->next;
    }
    else 
      return NULL;
  }

  while(pTail->next!=NULL)
  {
    pTail=pTail->next;
    pHead = pHead->next;
  }
  return pHead;
}

 

6. 求两个链表的公共节点

ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
  ListNode* p1 = pHead1;
  ListNode* p2 = pHead2;
  while(p1!=p2){
    p1 = (p1==NULL ? pHead2 : p1->next);
    p2 = (p2==NULL ? pHead1 : p2->next);
  }
  return p1;
}

 

7. 删除有序链表中的重复结点

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

采用递归法:

ListNode* deleteDuplication(ListNode* pHead)
{
    if(pHead==NULL) return NULL;
    if(pHead!=NULL && pHead->next==NULL) return pHead;
    
    ListNode* cur;
    if(pHead->next->val == pHead->val){
        cur=pHead->next->next;
        while(cur!=NULL && cur->val==pHead->val)
            cur=current->next;
        return deleteDuplication(cur);
    }
    else{
        cur = pHead->next;
        pHead->next = deleteDuplication(cur);
        return pHead;
    }
}

 

 

先总结到这里,后面再补充~