面试收集关于链表的一些面试题

链表相关的面试题是经常出现的,今天总结一下~

1.如何判断一个链表是否有环?如果有,找到环的入口?

设置快慢指针,快指针步长为2,慢指针步长为1,如果有环,最终快慢指针会相遇,代码如下:

bool hasCircle(Node* head, Node* &encounter)
{
         Node *fast = head, *slow = head;
         while(fast && fast->next)
        {
               fast = fast->next->next;
              slow = slow->next;
              if(fast == low)
               {
                   encounter = fast;
                   return true;
               }
          }
         // fast == NULL || fast->next == NULL
         encounter = NULL;
         return false;
}

至于如何确定环入口,请看下图

image

设入口点距链表头部head有x步,由于必然相遇,设相遇点距入口点有y步,环长度为r(表示环中有r+1个节点),那么有判断相遇的算法可知

(x+y)*2=nr+(x+y)   => nr=x+y  =>(n-1)r+r-y=x 

由上面的分析可知,设置两个指针,一个初始为head,另一个初始为encounter,同步前进,一旦相遇就是入口,于是有了下面的代码:

Node* findEntry(Node* head, Node* encounter)
{ 
          Node *p1 = head, *p2 = encounter;
         while(p1 != p2)
         {
                p1 = p1->next;
               p2 = p2->next;
          }
         return p1;
}

来源:http://hi.baidu.com/iwitggwg/item/7ad684119a27fefc9c778a5c

2. 假设有两个单链表,给出头指针 head1 和 head 2,判断两个链表是否有交点?

先判断是否有环,不过题目一般会给定无环条件。如果没有给出,可以判断,判断算法如下:

如果无环,遍历两个链表,得到两个链表的长度m和n,先遍历较长链表|m-n|次,接着同步遍历两个链表,一旦相同,则找到相同节点,否则返回null。

3. 只给定单链表中某个结点p(并非最后一个结点,即p->next!=NULL)指针,删除该结点。

这个题目有个trick,正常的思路是利用p前面一个节点才能删掉p,但是现在只有p的地址,得不到p前面的地址,那么只能删除p后面的节点,删除之前把p->next的内容复制到p中,注意这个算法删除不了最后一个元素。

4.只给定单链表中某个结点p,在p前面插入一个结点?

思路同3,先在p后面插入一个节点,然后将p的数据复制到p->next中,在将插入的结点的数据复制到p中

5.给定单链表的头结点head,删除链表中倒数第k个结点

同样是快慢指针问题,设置快指针p_fast,慢指针p_slow,初始均指向head,快指针先走k步,走完k步之后快慢指针同步走,每次走一步,直至快指针走到队尾。这里没有考虑有环的状况,有环这个题目就无法解了, 如果严谨的话要先判断是否有环。下面是一个不太严谨的算法。

Node *(Node *head,int k)
{
	assert(head!=NULL);
	Node *p_fast=head;
	Node *p_slow=head;
	int count=k;
	while(count>0&&p_fast!=NULL)
	{
		p_fast=p_fast->next;
		count--;
	}
	if(count>0)return NULL;
	while(p_fast!=NULL)
	{
		p_fast=p_fast->next;
		p_slow=p_slow->next;
	}
	return p_slow;
}

6.找出链表的中间元素(无环)

同样是快慢指针的问题,快指针慢指针初始都指向head,快指针每次行进两步,慢指针一步,一旦快指针指向链表尾部,慢指针指向的就是中间元素。实际写代码要考虑一些奇偶情况。

7.链表的就地逆置

头插法,但是要注意一些特殊情况,代码如下:

    Node* reverseList(Node* head)
  {
     Node *p1, *p2 , *p3;
  //链表为空,或是单结点链表直接返回头结点
     if (head == NULL || head->next == NULL)
    {
      return head;
    }
    p1 = head;
    p2 = head->next;
    while (p2 != NULL)
    {
      p3 = p2->next;
      p2->next = p1;
      p1 = p2;
      p2 = p3;
    }
    head->next = NULL;
    head = p1;
    return head;
  }

8.复杂链表的复制

这个是程序员面试精选的第49题,思路很巧妙,题目意思见链接,链表复杂就复杂在于除了有一个m_pNext指针指向下一个结点外,还有一个m_pSibling指向链表中的任一结点或者NULL。其结点的C++定义如下:

struct ComplexNode
{
    int m_nValue;
    ComplexNode* m_pNext;
    ComplexNode* m_pSibling;
};
程序员面试题精选100题(49)-复杂链表的复制 - 何海涛 - 微软、Google等面试题
 
解法一旦知晓就没什么神秘的了,答案参看链接。

9.已知两个链表head1 和head2 各自有序,请把它们合并成一个链表依然有序,保留所有结点,即便大小相同,递归实现,假设都是升序排列。

Node * merge(Node * head1,Node *head2)
{
	Node *ret=NULL;
	if(head1==NULL)return head2;
	if(head2==NULL)return head1;

	if(head1->val<head2->val)
	{
		ret=head1;
		ret->next=merge(head1->next,head2);
	}
	else
	{
		ret=head2;
		ret->next=merge(head1,head2->next);
	}
	return ret;
}

10.从一个未排序的链表中删除重复的元素,给出一种算法,如果要求不适用额外的空间呢?

如果能用hash表的话,只需要遍历一次链表,如果发现元素不在hash表中,就插入hash表,反之则删除。

如果不用额外的空间的话,有个O(N2)的算法:

LinkList RemoveDupNode(LinkList L)//删除重复结点的算法
{
    LinkList p,q,r;
    p=L->next;
    while(p)    // p用于遍历链表
    {
         q=p;
         while(q->next) // q遍历p后面的结点,并与p数值比较
         {
             if(q->next->data==p->data)
             {
                 r=q->next; // r保存需要删掉的结点
                 q->next=r->next;   // 需要删掉的结点的前后结点相接
                 free(r);
             }
             else
                 q=q->next;
         }
         p=p->next;
    }
    return L;
}
原文地址:https://www.cnblogs.com/obama/p/3065928.html