算法与acm

面试题目 链表专题

2009年9月6日 阅读(97)

面试的时候,书写程序要注意以下几点

1.确认了解题意,如果对题意了解不清,应该向面试人员问清楚
2.明确题意后,首先思考找到一个复杂度可以接受的正确算法,并表述出来,注意可以在草稿纸上写写划划,进行验证
3.观察复杂度能否再次降低
4.书写程序时,一定要认真,坚决防止出现逻辑错误,并根据程序具体分析可能的极端情况,处理好边界,并自己进行用例测试,以验证程序。

节点的定义如下:
typedef struct list {
int key;
struct list *next;
}list;

(1)已知链表的头结点head,写一个函数把这个链表逆序 ( Intel)

list * reverse(list * head){
list * h = head;
list * new_head = NULL,*temp;
if(h==NULL) return h;//如果是空链表
do{
   temp = h;
   h = h -> next;
   temp -> next = new_head;
   new_head = temp;
}while(h != NULL && h != head)//检测是循环链表
return new_head;
}

其实还要注意一点,链表内部是否包含小环。

(2)已知两个链表head1 和head2 各自有序,请把它们合并成一个链表依然有序。(保留所有结点,即便大小

相同)

list * merge (list *list1_head, list *list2_head){

}

其实需要问一下,head1 head2是否都是从大到小,这点一定要明确,不能默认两个是相同的规格排序。

(3)已知两个链表head1 和head2 各自有序,请把它们合并成一个链表依然有序,这次要求用递归方法进行。 (Autodesk)

list * merge (list *list1_head, list *list2_head){
   list * res;
   if(list1_head == NULL) return list2_head;
   if(list2_head == NULL) return list1_head; 
   if(list1_head->key > list2_head->key){
      res = list1_head;
      res->next = merge(list1_head->next,list2_head);

   }else{
      res = list2_head;
      res->next = merge(list1_head,list2_head->next);

   }
   return res;
}

 

(4)写一个程序,计算链表的长度

unsigned int list_len(list *head){
  unsigned int len = 0;
  list * h = head;
  if(h == NULL)return 0;
  d0{
      len++;
      h = h->next;
  }while(h != NULL && h != head) 
  return len;
}

如果是循环链表?

(5)有一个链表L,其每个节点有2个指针,一个指针next指向链表的下个节点,另一个random随机指向链表中的任一个节点,可能是自己或者为空,写一个程序,要求复制这个链表的结构并分析其复杂性。

这个题目的思路很巧妙。当然简单的方法,可以利用一个map或者hash,将链表的random指针的指向,保存起来。这样有O(n)存储空间的浪费,时间基本可以接近O(n).

实际上可以这样来做:我们将新的链表节点,插入到原来链表节点当中,并且修改原来的链表的random指针,使得该指针由我们现在的新节点所有。实际上形成下面这样一种结构,同时将原来o的random指针的值,复制给它后面的现在的@的random指针,该结构如下:

o->@->o->@->o->@->NULL

现在可以利用@拥有的random指针方便的找到它真正的random指针。因为原来的@的random指针指向o的random指针,只要把让@->random=@->random->next,random就是真正的那个指针了,然后我们再把@从这个链表中删除。

(6)给你一个单向链表的头指针,可能最后不是NULL终止,而是循环链表。题目问你怎么找出这个链表循环部分的第一个节点。

这个原来的判断链表环的扩展,需要求出环的开始点。只要稍微对原来的方法进行扩展,也就可以解决这个问题。使用两个指针一个步长为1,另一个步长为2,如果第二个指针可以追上第一个指针,则说明有环。这样我们首先可以求出环的长度L,然后在设两个步长为1的指针,让他们间距为L,这样第一个相等的地方,就是环的起点。

还有一个更好的方法,就是在步长为1,步长为2指针相遇后,再从头开始一个步长为1的指针,然后开始步长为1的指针继续行进,当这两个指针相遇的地方就是环的起点。

(7)(华为面试题)找出单向链表中中间结点
这个可以借鉴上面的方法,利用步长为1,步长为2指针,当步长为2的指针到达末尾时,指针1就刚好达到中间。

(8)题目:给定链表的头指针和一个结点指针,在O(1)时间删除该结点。链表结点的定义如下:
struct ListNode
{
      int        m_nKey;
      ListNode*  m_pNext;
};
函数的声明如下:
void DeleteNode(ListNode* pListHead, ListNode* pToBeDeleted);

实际上我们可以将该节点的下一个节点的值copy到这个节点,然后删除下一个节点。这样实际上就将一个不能处理的情况,通过一种技巧转化成了我们可以处理的正常情况。

但是还需要注意下面这个边界情况:如果要删除的是尾指针?这就意味着当前节点没有下一个节点。
通过这点我们也可以发现,如果认真考虑各个因素,思路清晰,还是很容易发现各种异常情况的,保证严谨的思考。

(9)如何检查一个单向链表上是否有环?

见(6)

(10)查找链表中倒数第k个结点

这个可以参考(7),我们可以设置步长相同,间距为k的指针,其特点在当内存无法存放所有元素,需要从硬盘读取时,性能卓越,k在可以接受的情况下可以一次读取到内存,大大提高了效率。

但是仍需要注意考虑边界情况:
(细节,如,假如链表的长度不足m,它就不存在m个元素,因此必须在两个指针的出发阶段检查最先出发的当前指针是否已经到了链表尾。)

(11)题目:两个单向链表,开头结点不一样, 在中间某处开始, 结点一样了,找出它们的第一个公共结点。

这个问题,可以通过判断最后一个节点是否相同,判断是否相交。如果相交,可以统计两个链表的长度,设一个为a,一个为b,然后这样再搞两个指针,一个先走|a-b|步,另一个再走,当它们相等时就是第一个公共结点。

 

再附三个有意思的小题,虽然不是链表相关的。

1.如何判断一个整数是不是完全平方数 (不允许开方). 单调函数可以二分查找。
2.不知长度的日志文件, 如何等概率选出100条。
3.一个唱片, 可以被分成8个大小一样的小扇形. 要求给每个扇形涂上红色或者蓝色, 使得唱片旋转起来以后, 按照顺序读出颜色序列, 就能判断唱片是顺时针旋转还是逆时针。

转载请注明作者:phylips@bmy 出处:http://duanple.blog.163.com/blog/static/70971767200986112015661/

You Might Also Like