昨天总结了一部分基础的面试题,有兴趣的小伙伴可以戳链接去看看链表面试题总结(一)。今天想要总结的是链表面试题的升级部分,关于链表带环部分和相交部分。
常见的面试题目:
- 合并两个有序链表, 合并后依然有序
- 求两个已排序单链表中相同的数据
- 判断单链表是否带环?若带环,求环的长度?求环的入口点?
- 判断两个链表是否相交,若相交,求交点。(假设链表不带环)
- 判断两个链表是否相交,若相交,求交点。(假设链表可能带环)
合并两个有序链表, 合并后依然有序
分析:首先它是一个有序链表,那么数据一定是规律的。我们创建新指针newHead用来保存合并后有序链表的头,用一个tail指针向后进行比较,指向新的节点。用tmp保存当前节点的信息,然后指针向后移动。直到其中一个链表被遍历完,直接用tail将另一个链表的后续部分链接起来。
SListNode* Merge(SListNode* l1, SListNode* l2) //合并两个有序链表,合并后依然有序
{
if (l1 == NULL) return l2;
if (l2 == NULL) return l1;
SListNode* newHead;
SListNode* tail;
SListNode* tmp;
if (l1->data < l2->data)
newHead = tail = l1;
else
newHead = tail = l2;
while ((l1!= NULL) && (l2 != NULL))
{
if (l1->data < l2->data)
{
tmp = l1;
l1 = l1->next;
}
else
{
tmp = l2;
l2 = l2->next;
}
tail->next = tmp;
tail = tmp;
}
if(l1) tail->next = l1;
else tail->next = l2;
return newHead;
}
求两个已排序单链表中相同的数据
分析:题目已经告诉我们是有序链表,所以一定要把这个条件用好。和上面的题目类似,直接对两个节点的数据进行比较,如果不相等,就向后移动,谁小就移动谁;如果相等,就直接打印此出来,直到遍历整个链表。由于较简单,就不画图说明了。
void UnionSet(SListNode* l1, SListNode* l2) //求两个已排序单链表中相同的元素
{
assert(l1 && l2);
while (l1 && l2)
{
if (l1->data < l2->data)
l1 = l1->next;
else if (l1->data > l2->data)
l2 = l2->next;
else
{
printf("%d ", l1->data);
l1 = l1->next;
l2 = l2->next;
}
}
}
判断单链表是否带环?若带环,求环的长度?求环的入口点?
1、判断单链表是否带环?
分析:先来判断一个链表是否带环,如果一个链表带环,它就没有尾节点,类似下图这种:
那么判断链表带环的问题就要从这里入手了,既然是一个环,那么肯定会在里面循环,所以就可以借鉴快慢指针问题了。定义一个快指针,一个慢指针,同时出发,如果可以相遇,那么就是环;如果不能相遇,则为非环链表。图示如下:
SListNode* IsCircle(SListNode* pHead) //判断一个链表是否带环
{
SListNode* fast, *slow;
fast = slow = pHead;
while (fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
if (fast == slow)
{
return slow;
}
}
return NULL;
}
2、若带环,求环的长度
分析:如果判断为环了,定义一个计数器count,那么环的长度就是沿着这个环在走一遍,再次走到现在的位置时,count的值。
size_t GetCircle(SListNode* meetNode) //如果带环,求环的长度
{
assert(meetNode);
int count = 1;
SListNode* cur = meetNode->next;
while (cur != meetNode)
{
cur = cur->next;
++count;
}
return count;
}
3、求环的入口点
分析:既然已经能确定是环,而且在第1问中,将相遇点返回了,那么我们就可以利用数学知识来求解入口点这个问题了。在这里,要说的方法还是快慢指针,这个题换成数学题,就变成了求相遇问题。下面用图来解释一下。
(关于为什么是一个走两步,一个走一步的原因:既然是要相遇,如果快指针走三步,它每次会跳过两个节点,慢指针一次只能走一步,这就可能会错过。快指针每次走两步的话,只会跳过一个节点,无论如何后都会和慢指针相遇。)
SListNode* GetEnter(SListNode* pHead, SListNode* meetNode) //求环的入口
{
SListNode* cur = pHead;
assert(cur && meetNode);
while (cur && meetNode)
{
if (cur == meetNode)
return meetNode;
cur = cur->next;
meetNode = meetNode->next;
}
return NULL;
}
判断两个链表是否相交,若相交,求交点。(假设链表不带环)
1、判断两个链表是否相交
分析:如果两个链表相交,那么它应该如下图所示。根据它的结构特点,我们可以发现,如果两个链表相交,它的尾节点一定是相同的。也就是说当两个链表的尾节点相同时,它们一定是相交的。
SListNode* IsUnion(SListNode* l1, SListNode* l2) //判断是否相交
{
assert(l1 && l2);
while (l1->next)
l1 = l1->next;
while (l2->next)
l2 = l2->next;
if (l1 == l2)
return l1;
return NULL;
}
2、若相交,求交点
分析:在链表中的相交相遇问题,貌似比较优的解法都是和快慢指针有关的。当然求交点,也是一样的。假设,两个链表一样长,那么它们的相交的判断方法就是在某处这两个节点的信息相同。好了,现在假设我们知道这两个链表的长度,让较长的那个先走两个链表长度之差步,那么接下来的处理方式就和一样长的链表的处理方式一样了。
SListNode* GetMeetNode(SListNode* l1, SListNode* l2) //如果相交,求交点
{
int len1 = 1;
int len2 = 1;
SListNode* cur1 = l1;
SListNode* cur2 = l2;
while (cur1->next)
{
cur1 = cur1->next;
++len1;
}
while (cur2->next)
{
cur2 = cur2->next;
++len2;
}
size_t k = abs(len1 - len2);
SListNode* longNode, *shortNode;
if (len1 > len2)
{
longNode = l1;
shortNode = l2;
}
else
{
longNode = l2;
shortNode = l1;
}
while (k--)
{
longNode = longNode->next;
}
while (longNode != shortNode)
{
longNode = longNode->next;
shortNode = shortNode->next;
}
return shortNode;
}
判断两个链表是否相交,若相交,求交点。(假设链表可能带环)
其实这个题目和上面的那个比较相似,甚至说是上面的延伸。在这种情况下,我们只需要理清可能出现的情况,然后根据对应的情况进行分析。
上面这六种是满足题设要求的情况。每一种情况对应的判断条件都会有所不同,在这里就只和大家分享一下我对这个题的理解,不做详细的分析。
常见的链表面试题就这么多了,其实现在回头看这些题,解题的思路都略相像。可能不同的是,每个题想要的考察侧重点,学会分析题目的思路很重要,对题设要求一定要尽可能考虑到所有的情况。