收集一些关于链表的常见面试笔试题。
链表结构:
typedef struct ListNode
{
int val;
ListNode* next;
}ListNode;
1. 链表反转
思路:将需要反转的结点的下一个结点暂存起来,然后将需要反转的结点与它指向的下一个结点交换指针位置,交换完毕以后,再将指针指向暂存的结点即可,这样相当于是改变了链表指针的指向,同时要注意到,返回值是原先链表的链表尾,可以通过判断下一结点是否为空,如果为空则说明到达链表末尾,此时就可以将结果返回,链表已经被反转成功。
ListNode* reverseList(ListNode* pHead)
{
if (pHead == NULL || pHead->next == NULL) //链表为空或者仅1个数直接返回
return pHead;
ListNode* p = pHead, *res= NULL; //定义一个节点,保存最终结果
ListNode* temp;
while (p != NULL) //循环到链尾
{
temp = p->next; //暂存p下一个地址,防止变化指针指向后找不到后续的数
p->next = res; //p->next指向前一个空间
res = p; //新链表的头移动到p,扩长一步链表
p = temp; //p指向原始链表p指向的下一个空间
}
return res;
}
2. 从尾到头输出单链表
思路:
1)从头到尾遍历链表,每经过一个节点的时候,把该节点放到栈中。当遍历完整个链表后,再从栈顶开始输出结点的值,此时输出的节点的顺序已经反转过来了。该方法需要维护一个额外的栈空间;
2)既然想到了栈来实现这个函数,而递归本质上就是一个栈结构,于是可以用递归来实现。要实现反过来输出链表,每访问一个节点的时候,先递归输出到它后面的节点,再输出该节点自身,这样即链表的输出结果就反过来了。
思路1实现:
void PrintListReverse(ListNode *pHead)
{
stack<ListNode*> s_node;
ListNode* tmp = pHead;
while(temp != NULL)
{
s_node.push(temp);
temp = temp->next;
}
while(!s_node.empty())
{
printf("%d",s_node.top()->val);
s_node.pop();
}
}
思路2:
void PrintListReverse(ListNode *pHead)
{
if(pHead != NULL)
{
PrintListReverse(pHead->next); //先递归输出到它后面的节点
}
printf("%d", pHead->val); //输出后一个节点自身
}
此题还有两个常见的变体:
(1)从尾到头输出一个字符串;
(2)定义一个函数求字符串的长度,要求该函数体内不能声明任何变量;
对于这两个变体,都可以参考思路2的实现方式。
3. 合并两个有序链表
思路:逐个比较两个链表的元素,较小的元素进入到新的链表中,并且指向它的下一个。递归法实现。
ListNode* Merge(ListNode* n1, ListNode* n2)
{
if (n1 == NULL) {
return n2;
} else if (n2 == NULL) {
return n1;
}
ListNode* res = NULL;
if (n1->val < n2->val)
{
res = n1;
res->next = Merge(n1->next, n2);
} else {
res = n2;
res->next = Merge(n1, n2->next);
}
return res;
}
4. 判断单链表是否有环
此题有很多种方法,这里介绍一种比较巧妙的方法,使用STL中的map实现。
思路:首先定义 map<ListNode*,int> m; 将一个ListNode*指针映射成数组的下标,并赋值为一个int类型的数值。然后从链表的头指针开始往后遍历,每次遇到一个指针p,就判断m[p]是否为0。如果为0,则将m[p]赋值为1,表示该节点是第一次访问;而如果m[p]的值为1,则说明这个节点已经被访问过一次了,于是就形成了环。
map<ListNode*, int> m;
bool IsLoop(ListNode *phead)
{
if(pHead == NULL)
return false;
ListNode* p = pHead;
while(p != NULL)
{
if(m[p] == 0)
m[p] = 1;
else if(m[p] == 1)
return true;
p = p->next;
}
}
延伸:找到有环链表的环入口
思路:
//断链法:破坏链表结构
/*
时间复杂度为O(n),两个指针,一个在前面(front),另一个(pre)紧邻着这个指针,在后面。
两个指针同时向前移动,每移动一次,令前面的指针的next指向NULL。
也就是说:访问过的节点都断开,最后到达的那个节点一定是尾节点的下一个,
也就是循环的第一个。
这时候已经是第二次访问循环的第一节点了,第一次访问的时候我们已经让它指向了NULL,
所以到这结束。
*/
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
if(!pHead->next)
return NULL;
ListNode* pre = pHead;
ListNode* front = pHead->next;
while(front){
pre->next = NULL;
pre=front;
front = front->next;
}
return pre;
}
5. 找到链表的倒数第k个节点
思路:创建两个指针,先让第一个指针和第二个指针都指向头结点,然后再让第一个指针走(k-1)步,到达第k个节点,然后两个指针同时往后移动,当第一个结点到达末尾的时候,第二个结点所在位置就是倒数第k个节点了
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
ListNode *pHead = pListHead;
ListNode *pTail = pListHead;
if(pListHead==NULL || k==0)
return NULL;
for(int i=1;i<k;++i){
if(pTail->next != NULL)
{
pTail=pTail->next;
}
else
return NULL;
}
while(pTail->next!=NULL)
{
pTail=pTail->next;
pHead = pHead->next;
}
return pHead;
}
6. 求两个链表的公共节点
ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
ListNode* p1 = pHead1;
ListNode* p2 = pHead2;
while(p1!=p2){
p1 = (p1==NULL ? pHead2 : p1->next);
p2 = (p2==NULL ? pHead1 : p2->next);
}
return p1;
}
7. 删除有序链表中的重复结点
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
采用递归法:
ListNode* deleteDuplication(ListNode* pHead)
{
if(pHead==NULL) return NULL;
if(pHead!=NULL && pHead->next==NULL) return pHead;
ListNode* cur;
if(pHead->next->val == pHead->val){
cur=pHead->next->next;
while(cur!=NULL && cur->val==pHead->val)
cur=current->next;
return deleteDuplication(cur);
}
else{
cur = pHead->next;
pHead->next = deleteDuplication(cur);
return pHead;
}
}
先总结到这里,后面再补充~