链表面试题小结

来源:互联网 发布:json数组长度 编辑:程序博客网 时间:2024/06/05 08:12

转自:http://blog.sina.com.cn/s/blog_54b2ce380100uqwr.html

某本书上面说了,链表这个东西,实际用的并不多,但是可以提供很好的考察面试者编程技巧和思维能力的素材。这里总结一下,见过的面试题和对应的候选解法。

题一、 给定单链表,检测是否有环。
    使用两个指针p1,p2从链表头开始遍历,p1每次前进一步,p2每次前进两步。如果p2到达链表尾部,说明无环,否则p1、p2必然会在某个时刻相遇(p1==p2),从而检测到链表中有环。

http://ostermiller.org/find_loop_singly_linked_list.html

这篇文章讲了很多好的坏得相关算法。

题二、 给定两个单链表(head1, head2),检测两个链表是否有交点,如果有返回第一个交点。
   如果head1==head2,那么显然相交,直接返回head1。
   否则,分别从head1,head2开始遍历两个链表获得其长度len1与len2。假设len1>=len2,那么指针p1由head1开始向后 移动len1-len2步。指针p2=head2,下面p1、p2每次向后前进一步并比较p1p2是否相等,如果相等即返回该结点,否则说明两个链表没有 交点。

题三、 给定单链表(head),如果有环的话请返回从头结点进入环的第一个节点。
   运用题一,我们可以检查链表中是否有环。
   如果有环,那么p1p2重合点p必然在环中。从p点断开环,方法为:p1=p, p2=p->next, p->next=NULL。此时,原单链表可以看作两条单链表,一条从head开始,另一条从p2开始,于是运用题二的方法,我们找到它们的第一个交点即为所求。

    也可以不断开环。设重合点为p3,从p3开始遍历这个环,同时从表头开始走,检查每步是否在那个环中。这个方法大概有nlogn。

   使用快慢指针,第一次相遇,表明存在循环。继续快慢指针,第二次相遇,得到的iteration步长为环的长度。分别从相遇点和第一个节点出发,都是步长为1的指针,当相遇时,得到的iteration步长为环首的位置。

题四、只给定单链表中某个结点p(并非最后一个结点,即p->next!=NULL)指针,删除该结点。
   办法很简单,首先是放p中数据,然后将p->next的数据copy入p中,接下来删除p->next即可。

题五、只给定单链表中某个结点p(非空结点),在p前面插入一个结点。
   办法与前者类似,首先分配一个结点q,将q插入在p后,接下来将p中的数据copy入q中,然后再将要插入的数据记录在p中。

题六、给定单链表头结点,删除链表中倒数第k个结点。
   使用两个节点p1,p2,p1初始化指向头结点,p2一直指向p1后第k个节点,两个结点平行向后移动直到p2到达链表尾部(NULL),然后根据p1删除对应结点。
题七、链表排序

   链表排序最好使用归并排序算法。堆排序、快速排序这些在数组排序时性能非常好的算法,在链表只能“顺序访问”的魔咒下无法施展能力;但是归并排序却如鱼得水,非但保持了它O(nlogn)的时间复杂度,而且它在数组排序中广受诟病的空间复杂度在链表排序中也从O(n)降到了O(1)。真是好得不得了啊,哈哈。以上程序是递推法的程序,另外值得一说的是看看那个时间复杂度,是不是有点眼熟?对!这就是分治法的时间复杂度,归并排序又是divide and conquer。 

double cmp(ListNode *p ,ListNode *q)

{return (p->keyVal - q->keyVal);}

ListNode* mergeSortList(ListNode *head)

   
    ListNode *p, *q, *tail, *e;   
    int nstep = 1;    
    int nmerges = 0;    
    int i;    
    int psize, qsize;
   
    if (head == NULL || head->next == NULL)        
    {return head;}    
    while (1)        
    {   p = head;    
    tail = NULL;
    nmerges = 0;   
    while (p)        
    {   nmerges++;  q = p;  psize = 0;    
    for (i = 0; i < nstep; i++){        
        psize++;        
        q = q->next;        
        if (q == NULL)break;        
    }    
    qsize = nstep;    
    while (psize >0 || (qsize >0 && q))        
    {        
        if (psize == 0 ){e = q; q = q->next; qsize--;}
       
        elseif (q == NULL || qsize == 0){e = p; p = p->next; psize--;}
       
        elseif (cmp(p,q) <= 0){e = p; p = p->next; psize--;}
       
        else{e = q; q = q->next; qsize--;}
       
        if (tail != NULL){tail->next = e;}
       
        else{head = e;}
       
        tail = e;
       
    }    
    p = q;

    }    
    tail->next = NULL;    
    if (nmerges <= 1){return head;}   
    else{nstep <<= 1;}    
    }   
}

另外一篇博客,链表的归并排序:http://blog.csdn.net/lalor/article/details/7430624 贴在下面

当我们需要对链表进行排序时,由于不能对它的元素进行随机访问,所以更适合使用归并排序,大名鼎鼎的快速排序用到链表上,效率也很低,原因还是在于不能对链表中的元素进行随机访问,同理,采用堆排序更是不可能的事情。

        算法具体实现时需要一个指向头节点(链表的第一个节点,链表中不包含额外的一个节点来作头节点)的指针,这是因为在算法实现的时候,不大可能第一个节点正好就是所有元素中最小的一个,则链表的头节点会改变,因此我们需要一个指向头节点的指针来存储不断变化的头节点。

算法思想:

MergeSort(headRef)
1) If head is NULL or there is only one element in the Linked List    then return.2) Else divide the linked list into two halves.      FrontBackSplit(head, &a, &b); /* a and b are two halves */3) Sort the two halves a and b.      MergeSort(a);      MergeSort(b);4) Merge the sorted a and b (using SortedMerge() discussed here)   and update the head pointer using headRef.     *headRef = SortedMerge(a, b);
代码示例:
#include <stdio.h>  #include <stdlib.h>    /*Link list node*/  struct node  {      int data;      struct node* next;  };    /*function prototype */  struct node* SortedMerge(struct node* a, struct node* b);  void FrontBackSplit(struct node* source, struct node** frontRef, struct node** backRef);    /*sorts the linked list by changing next pointers(not data) */  void MergeSort(struct node** headRef)  {      struct node* head = *headRef;      struct node* a;      struct node* b;        /*base case-- length 0 or 1 */      if((head == NULL) || (head->next == NULL))      {          return;      }            /*Split head into 'a' and 'b' sublists */      FrontBackSplit(head, &a, &b);        /*Recursively sort the sublists */      MergeSort(&a);      MergeSort(&b);        /* answer = merge the two sorted lists together */      *headRef = SortedMerge(a, b);  }    struct node* SortedMerge(struct node* a, struct node* b)  {      struct node* result = NULL;        /* Base cases */      if(a == NULL)          return (b);      else if(b == NULL)          return (a);        /* Pick either a or b recur */      if(a->data <= b->data)      {          result = a;          result->next = SortedMerge(a->next, b);      }      else      {          result = b;          result->next = SortedMerge(a, b->next);         }      return (result);  }    /* UTILITY FUNCTIONS */  /* Split the nodes of the given list into front and back halves,     and return the two lists using the references parameters.     If the length is odd, the extra node shold go in the front list.     Uses the fast/slow pointer strategy. */  void FrontBackSplit(struct node* source, struct node** frontRef, struct node** backRef)  {      struct node* fast;      struct node* slow;        if(source == NULL || source->next == NULL)      {          *frontRef = source;          *backRef = NULL;      }      else      {          slow = source;          fast = source->next;            /* Advance 'fast' two nodes, and advance 'slow' one node */           while(fast != NULL)          {              fast = fast->next;              if( fast != NULL )              {                  slow = slow->next;                  fast = fast->next;              }          }            *frontRef = source;          *backRef = slow->next;          slow->next = NULL;      }  }        /*Function to print nodes in a given linked list*/  void printList(struct node* node)  {      while( node != NULL )      {          printf("%d  ", node->data);          node = node->next;      }  }    /* Function to insert a node at the begining of the linked list*/  void push(struct node** head_ref, int new_data)  {      /*allocate node*/      struct node* new_node = (struct node*)malloc(sizeof(struct node));            /*put in the data*/      new_node->data = new_data;            /*link the old list off the new node*/      new_node->next = (*head_ref);            /*move the head to point to the new node*/      (*head_ref) = new_node;  }        /* Drier program to test above functions*/  int main()  {      /* Start with the empty list */      struct node* res = NULL;      struct node* a = NULL;        /* Let us create a unsorted linked lists to test the functions        Created lists shall be a: 2->3->20->5->10->15 */      push(&a, 15);      push(&a, 10);      push(&a, 5);      push(&a, 20);      push(&a, 3);      push(&a, 2);         /* Sort the above created Linked List */      MergeSort(&a);        printf("\n Sorted Linked List is: \n");      printList(a);                   return 0;  } 

题八、倒转单链表

给出非递归和递归解法:

#include <iostream>

using namespace std;

struct Node
{
   
 int data;
   
 Node* next;
}*head;

// 非递归写法
Node* InverseLinkedList(Node* head)
{
   
 if(head == NULL)
       
 return NULL;
   
 Node* newHead = NULL;
   
 while(head != NULL)
   
 {
       
 Node* nextNode = head->next;
        head
->next = newHead;
        newHead
 = head;
        head
 = nextNode;
   
 }
   
 return newHead;
}

// 递归写法
Node* InverseLinkedListRecur(Node* head)
{
   
 if(head == NULL)
       
 return NULL;
   
 if(head->next == NULL)
       
 return head;
   
 Node* newHead = InverseLinkedListRecur(head->next);
   
 Node *tmp = newHead;
   
 while(tmp->next != NULL)
   
 {
        tmp
 = tmp->next;
   
 }
    tmp
->next = head;
    head
->next = NULL;
   
 return newHead;
}

int main()
{
   
 // 构建链表 9->8->7->6->5->4->3->2->1->0->NULL
   
 for(int i = 0; i < 10; i++)
   
 {
       
 Node* node = new Node();
        node
->data = i;
        node
->next = head;
        head
 = node;
   
 }
    head
 = InverseLinkedList(head);
   
 Node *tmp = head;
   
 while(head != NULL)
   
 {
        cout
 << head->data << " ";
        head
 = head->next;
   
 }
    cout
 << endl;
    head
 = InverseLinkedListRecur(tmp);
    tmp
 = head;
   
 while(head != NULL)
   
 {
        cout
 << head->data << " ";
        head
 = head->next;
   
 }
    cout
 << endl;
   
 while(tmp != NULL)
   
 {
       
 Node* next = tmp->next;
       
 delete tmp;
        tmp
 = next;
   
 }
}
题九、两个有序链表的合并

   有两个有序链表,各自内部是有序的,但是两个链表之间是无序的

typedef struct node{    int data;    struct node * next;}* List;List mergeSortedLinkList(List list1, List list2){    List pList1,pList2,mergedList,pCurNode;    if (list1 == NULL)    {        return list2;    }    if (list2 == NULL)    {        return list1;    }    pList1 = list1;    pList2 = list2;    mergedList = NULL;    if (pList1==pList2)    {               mergedList = pList1;        pList1 = pList1->next;        pList2 = pList2->next;    }    else     {        if (pList1->data <= pList2->data)        {            mergedList = pList1;            pList1 = pList1->next;        }        else        {            mergedList = pList2;            pList2 = pList2->next;        }    }    pCurNode = mergedList;    while(pList1 && pList2)    {        if (pList1==pList2)        {            pCurNode->next = pList1;            pCurNode = pList1;            pList1 = pList1->next;            pList2 = pList2->next;        }        else        {            if (pList1->data <= pList2->data)            {                pCurNode->next = pList1;                pCurNode = pList1;                pList1 = pList1->next;            }            else            {                pCurNode->next = pList2;                pCurNode = pList2;                pList2 = pList2->next;            }        }    }    pCurNode->next =pList1?pList1:pList2;    return mergedList;}题十、找出链表的中间元素
单链表的一个比较大的特点用一句广告语来说就是“不走回头路”,不能实现随机存取(random access)。如果我们想要找一个数组a的中间元素,直接a[len/2]就可以了,但是链表不行,因为只有a[len/2 - 1] 知道a[len/2]在哪儿,其他人不知道。因此,如果按照数组的做法依样画葫芦,要找到链表的中点,我们需要做两步(1)知道链表有多长(2)从头结点开始顺序遍历到链表长度的一半的位置。这就需要1.5n(n为链表的长度)的时间复杂度了。有没有更好的办法呢?有的。想法很简单:两个人赛跑,如果A的速度是B的两倍的话,当A到终点的时候,B应该刚到中点。这只需要遍历一遍链表就行了,还不用计算链表的长度。
0 0
原创粉丝点击