1. 从无序链表中删除重复元素 来源 Cracking the Code Interview (2nd Edition) Ch2 Q1.
【解答】:题目已经明确说明是无序列表,对这类面试题,一般最差的复杂度是 O(NlogN),也就是需要一次排序过程,所以给出的答案最好要优于O(NlogN)。
方法一:除去重复元素,首先想到利用hash。将List迭代一遍,每拿出一个元素,放到hashtable里,并判断是否重复,如果重复则从List中删除该元素。这个算法的时间复杂度是O(N),但需要O(N)的空间建立hashtable去存放元素。
方法二:利用runner pointer. 这个技巧也是解答LinkedList Problem的重要技巧之一。迭代当前链表,每次迭代利用一个runner去访问之后的元素,删除重复元素。算是naive approach,时间复杂度O(N^2),空间复杂度O(1)。面试中最先可能想到的应该是这种解答,然后可以优化到第一种方法。
2. 给定单链表,检测是否有环。如果有环,则求出进入环的第一个节点。
判断单向链表是否有环,可以采用快指针与慢指针的方式来解决。即定义一个快指针fast和一个慢指针slow,使得fast每次跳跃两个节点,slow每次跳跃一个节点。如果链表没有环的话,则slow与fast永远不会相遇(这里链表至少有两个节点);如果有环,则fast与slow将会在环中相遇。判断出链表有环以后,则需要算出进入环的第一个节点。在fast与slow第一次相遇后,设置一个节点pNode从链表的头部开始遍历,每次只遍历一个节点。这样,当fast与slow再次相遇时,pNode所处的位置便是环的首部。以下该问题的实现源码(C语言描述):
- LNode* GetLoopNode(LNode* head)
- {
- //前置条件的判断
- if (!head)
- {
- return NULL;
- }
- //定义一个快指针和一个慢指针
- LNode* fast = head;
- LNode* slow = head;
- while (fast && (fast->next))
- {
- fast = fast->next->next;
- slow = slow->next;
- if (fast == slow)
- {
- //如果有环,则返回环的第一个节点
- slow = head;
- while (1)
- {
- fast = fast->next;
- slow = slow->next;
- if (fast == slow)
- {
- break;
- }
- }
- return slow;
- }
- }
- return NULL;
- }
3. 给定两个单链表,检测两个链表是否有交点,如果有返回第一个交点。
检测两个单链表是否有交点是很容易的,因为如果两个链表有交点,那么这两个链表的最后一个节点必须相同,所以只需比较最后一个节点即可。但是这种方案是无法求出第一个交点的,所以需要另辟蹊径。另外,判断是否有交点可以转换成链表是否有环的问题。让一个链表的最后一个节点指向第二个链表的头结点,这样的话,如果相交,则新的链表是存在环的,并且交点便是环的入口点。
ListNode* GetFirstCommonNode(ListNode * pHead1, ListNode * pHead2)
{
if(pHead1 == NULL || pHead2 == NULL)
return NULL;
int len1 = 1;
ListNode * pTail1 = pHead1;
while(pTail1->m_pNext != NULL)
{
pTail1 = pTail1->m_pNext;
len1++;
}
int len2 = 1;
ListNode * pTail2 = pHead2;
while(pTail2->m_pNext != NULL)
{
pTail2 = pTail2->m_pNext;
len2++;
}
if(pTail1 != pTail2) // 不相交直接返回NULL
return NULL;
ListNode * pNode1 = pHead1;
ListNode * pNode2 = pHead2;
// 先对齐两个链表的当前结点,使之到尾节点的距离相等
if(len1 > len2)
{
int k = len1 - len2;
while(k--)
pNode1 = pNode1->m_pNext;
}
else
{
int k = len2 - len1;
while(k--)
pNode2 = pNode2->m_pNext;
}
while(pNode1 != pNode2)
{
pNode1 = pNode1->m_pNext;
pNode2 = pNode2->m_pNext;
}
return pNode1;
}
4. 只给定单链表中某个结点p(并非最后一个结点,即p->next!=NULL)指针,删除该结点。
由于没有给定链表的头结点,所以无法获取需要删除节点的前驱结点,这样就无法使用常规的方法去解决。在《编程之美》这本书里面给出一个非常精妙的解法,也就是采用了狸猫换太子的思想。以下是实现源码:
- void DeleteNode(LNode* pNode)
- {
- //如果pNode是最后一个节点,则该问题误解
- if (!pNode || !(pNode->next))
- {
- return;
- }
- //获取pNode的后驱节点
- LNode* qNode = pNode->next;
- //将qNode的值赋给pNode,并删除qNode
- pNode->data = qNode->data;
- pNode->next = qNode->next;
- free(qNode);
- }
5. 给定单链表头结点,删除链表中倒数第k个结点
这个问题的关键是需要获取倒数第k个节点。如何获取呢?这里,需要两个指针p和q,p指向头结点,q指向距离头结点为k的节点。这样p和q每次遍历一个节点,当q遍历到末尾的时候,p指向的节点即为倒数第k个节点。然后删除即可。以下是实现源码(这里获取的是倒数第k+1个节点,因为需要删除的是倒数第k个节点):
- void DelLastKNode(unsigned int k, LNode* head)
- {
- //前置条件判断
- if (!head || k <= 0)
- {
- return;
- }
- LNode* pNode = head;
- LNode* qNode = head;
- //将qNode遍历到正数第k + 1个节点
- unsigned int i;
- for (i = 0; i < k; i++)
- {
- qNode = qNode->next;
- }
- //获取倒数第k + 1个节点
- while (qNode->next)
- {
- pNode = pNode->next;
- qNode = qNode->next;
- }
- //删除倒数第k个节点
- LNode* temp = pNode->next;
- pNode->next = temp->next;
- free(temp);
- }
6. 判断链表是不是回文链表。来源 Cracking the Code Interview (2nd Edition) Ch2 Q7.
【解答】:经典面试题了。回文就是 0-->1-->2-->1-->0 奇数个节点, 或者 0-->1-->2-->2-->1-->0 偶数个节点。
方法一:很自然的想法就是把链表反转,然后对比反转以后的链表和原链表的节点值,并且只要比较反转链表和原链表一半的元素就可以。
方法二:利用runner pointer。很多的题目都可以利用这个方法来解决。Runner pointer和递归是解决链表问题最常用的两个方法。
定义两个指针,slow runner和fast runner,fast以2倍于slow的速度向链表尾部移动。如有奇数个节点:当fast到达链表尾部时,slow恰好到达链表中间。如有偶数个节点:fast第一次为null时,slow恰好完成了一半节点的访问。把slow访问过的节点元素值压入一个stack里。此后slow继续向链表尾部访问,同时stack里的元素开始出栈(奇数个元素的情况下要跳过中间元素),比较两个元素值,一旦出现不等,则不是回文,否则是。该解法的时间复杂度是O(N),因为需要一次迭代,同时需要O(N)的空间用作stack来存放链表元素。
7.反转单链表
#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;
}
8.链表排序
9. 从尾到头打印单链表
对于这种颠倒顺序的问题,我们应该就会想到栈,后进先出。所以,这一题要么自己使用栈,要么让系统使用栈,也就是递归。注意链表为空的情况。时间复杂度为O(n)。参考代码如下:
自己使用栈:
1 // 从尾到头打印链表,使用栈
2 void RPrintList(ListNode * pHead)
3 {
4 std::stack<ListNode *> s;
5 ListNode * pNode = pHead;
6 while(pNode != NULL)
7 {
8 s.push(pNode);
9 pNode = pNode->m_pNext;
10 }
11 while(!s.empty())
12 {
13 pNode = s.top();
14 printf("%d\t", pNode->m_nKey);
15 s.pop();
16 }
17 }
使用递归函数:
1 // 从尾到头打印链表,使用递归
2 void RPrintList(ListNode * pHead)
3 {
4 if(pHead == NULL)
5 {
6 return;
7 }
8 else
9 {
10 RPrintList(pHead->m_pNext);
11 printf("%d\t", pHead->m_nKey);
12 }
13 }
10. 已知两个单链表pHead1 和pHead2 各自有序,把它们合并成一个链表依然有序
这个类似归并排序。尤其注意两个链表都为空,和其中一个为空时的情况。只需要O(1)的空间。时间复杂度为O(max(len1, len2))。参考代码如下:
1 // 合并两个有序链表
2 ListNode * MergeSortedList(ListNode * pHead1, ListNode * pHead2)
3 {
4 if(pHead1 == NULL)
5 return pHead2;
6 if(pHead2 == NULL)
7 return pHead1;
8 ListNode * pHeadMerged = NULL;
9 if(pHead1->m_nKey < pHead2->m_nKey)
10 {
11 pHeadMerged = pHead1;
12 pHeadMerged->m_pNext = NULL;
13 pHead1 = pHead1->m_pNext;
14 }
15 else
16 {
17 pHeadMerged = pHead2;
18 pHeadMerged->m_pNext = NULL;
19 pHead2 = pHead2->m_pNext;
20 }
21 ListNode * pTemp = pHeadMerged;
22 while(pHead1 != NULL && pHead2 != NULL)
23 {
24 if(pHead1->m_nKey < pHead2->m_nKey)
25 {
26 pTemp->m_pNext = pHead1;
27 pHead1 = pHead1->m_pNext;
28 pTemp = pTemp->m_pNext;
29 pTemp->m_pNext = NULL;
30 }
31 else
32 {
33 pTemp->m_pNext = pHead2;
34 pHead2 = pHead2->m_pNext;
35 pTemp = pTemp->m_pNext;
36 pTemp->m_pNext = NULL;
37 }
38 }
39 if(pHead1 != NULL)
40 pTemp->m_pNext = pHead1;
41 else if(pHead2 != NULL)
42 pTemp->m_pNext = pHead2;
43 return pHeadMerged;
44 }
1 ListNode * MergeSortedList(ListNode * pHead1, ListNode * pHead2)
2 {
3 if(pHead1 == NULL)
4 return pHead2;
5 if(pHead2 == NULL)
6 return pHead1;
7 ListNode * pHeadMerged = NULL;
8 if(pHead1->m_nKey < pHead2->m_nKey)
9 {
10 pHeadMerged = pHead1;
11 pHeadMerged->m_pNext = MergeSortedList(pHead1->m_pNext, pHead2);
12 }
13 else
14 {
15 pHeadMerged = pHead2;
16 pHeadMerged->m_pNext = MergeSortedList(pHead1, pHead2->m_pNext);
17 }
18 return pHeadMerged;
19 }