链表是数据结构中最常见的一种结构了,由链表而产生的面试题也是十分重要的,所以我总结了常见的链表面试题并对其进行实现:
1.从尾到头打印单链表
//从尾到头打印单链表,利用辅助栈的结构
void ReverseShowList(ListNode *head)
{
stack<ListNode *> s;
ListNode *cur=head;
while(cur)
{
s.push(cur);
cur=cur->next;
}
while(!s.empty())
{
ListNode *top=s.top();
cout<<top->data<<" ";
s.pop();
}
cout<<endl;
}
2.逆置/反转单链表
//反转单链表
ListNode *ReverseList(ListNode *head)
{
assert(head || head->next); //头指针为空或者只有一个结点
ListNode *cur=head;
ListNode *newHead=NULL;
ListNode *newCur=NULL;
while(cur)
{
newCur=cur;
cur=cur->next;
newCur->next=newHead;
newHead=newCur; //更新头
}
return newHead;
}
3.删除无头单链表的非尾结点
//删除无头单链表的非尾结点,替换法
void DelNotTail(ListNode *pos)
{
assert(pos && pos->next);
ListNode *del=pos->next;
pos->data=del->data;
pos->next=del->next;
delete del;
del=NULL;
}
4.在无头单链表的一个非头结点前插入一个结点
//在当前结点前插一个结点(无头单链表),实现方法类似删除无头单链表的非尾结点
void InsertFrontNode(ListNode *pos,int d)
{
assert(pos);
ListNode *newNode=new ListNode(d);
newNode->next=pos->next;
pos->next=newNode;
int tmp=newNode->data;
newNode->data=pos->data;
pos->data=tmp;
}
5.查找链表的中间节点,只允许遍历一遍链表
//查找链表的中间节点,只允许遍历一遍链表
ListNode *FindMidNode(ListNode *head)
{
ListNode *fast=head; //快慢指针
ListNode *slow=head;
while(fast && fast->next)
{
fast=fast->next->next;
slow=slow->next;
}
return slow;
}
6.删除倒数第k个结点,只允许遍历一次链表
//删除倒数第k个结点,只允许遍历一遍链表
void DelKNode(ListNode *head,int k)
{
assert(head);
ListNode *prev=head;
ListNode *slow=head;
while(prev)
{
k--;
//slow指向倒数第k个结点
if(k < 0)
{
slow=slow->next;
}
prev=prev->next; //prev先指向正数第k个结点
}
if(k <= 0) //删除slow所指向的结点
{
DelNotTail(slow);
//ListNode *del=slow->next;
//slow->data=del->data;
//slow->next=del->next;
//delete del;
//del=NULL;
}
}
7.查找链表的中间结点,只允许遍历一遍链表
//查找链表的中间节点,只允许遍历一遍链表
ListNode *FindMidNode(ListNode *head)
{
ListNode *fast=head; //快慢指针
ListNode *slow=head;
while(fast && fast->next)
{
fast=fast->next->next;
slow=slow->next;
}
return slow;
}
8.合并两个有序的单链表,使其合并后依然有序,不允许开辟新的辅助空间。非递归和递归版本的实现
//非递归实现
//合并两个有序的单链表,不开辟新的空间
ListNode *MergeNoR(ListNode *plist1,ListNode *plist2)
{
ListNode *p1=plist1;
ListNode *p2=plist2;
ListNode *newHead=NULL;
ListNode *tail=NULL;
if(p1 == p2) //三种特殊情况
return p1;
if(p1 == NULL)
return p2;
if(p2 == NULL)
return p1;
if(p1->data < p2->data) //为新的链表选取新的头指针
{
newHead=p1;
tail=p1;
p1=p1->next;
}
else
{
newHead=p2;
tail=p2;
p2=p2->next;
}
while(p1 != NULL && p2 != NULL)
{
if(p1->data < p2->data) //选取适合的结点插入
{
tail->next=p1;
tail=tail->next;
p1=p1->next;
}
else
{
tail->next=p2;
tail=tail->next;
p2=p2->next;
}
}
if(p1 != NULL) //有可能有的链表的部分数据尚未完全插入
tail->next=p1;
if(p2 != NULL)
tail->next=p2;
return newHead;
}
//递归实现
ListNode *MergeR(ListNode *plist1,ListNode *plist2)
{
ListNode *p1=plist1;
ListNode *p2=plist2;
ListNode *plist3=NULL;
if(p1 == p2)
return p1;
if(p1 == NULL)
return p2;
if(p2 == NULL)
return p1;
if(p1->data < p2->data)
{
plist3=p1;
plist3->next=MergeR(p1->next,p2);
}
else
{
plist3=p2;
plist3->next=MergeR(p1,p2->next);
}
return plist3;
}
9.单链表实现约瑟夫环
//单链表实现约瑟夫环
ListNode *JosephCycle(ListNode *head,int k)
{
assert(head);
ListNode *cur=head;
while(cur->next != cur) //有超过一个人存在
{
int m=k; //m保存k的值,可以重复使用
while(--m) //cur指向将要退出的结点
{
cur=cur->next;
}
cout<<"del:"<<cur->data<<endl;
ListNode *del=cur->next; //利用交换的方式,最后要删除的结点是cur的下一个结点
cur->data=del->data;
cur->next=del->next;
delete del;
del=NULL;
}
return cur;
}
10.判断单链表是否带环?若带环则求出链表长度?以及环的入口点?
对这道面试题更加详细的介绍可参考我的这篇博客:
//判断链表是否带环,带环则返回相遇点
ListNode *CheckCycle(ListNode *head)
{
assert(head);
ListNode *fast=head; //快慢指针
ListNode *slow=head;
while(fast && fast->next)
{
fast=fast->next->next;
slow=slow->next;
if(fast == slow)
return slow;
}
return NULL;
}
//如果带环则求出链表长度
int GetCircleLength(ListNode *head)
{
//从相遇点开始遍历,当再次遇到相遇点时即遍历完整个环
assert(head);
ListNode *meet=CheckCycle(head);
assert(meet);
ListNode *cur=meet;
int count=1;
while(cur->next != meet)
{
count++;
cur=cur->next;
}
return count;
}
//求出带环链表的入口节点
ListNode *GetCircleEntryNode(ListNode *head,ListNode *meet)
{
assert(head);
assert(meet);
//prev从相遇点开始,slow从链表头开始
//prev和slow的再次相遇点就是入口节点
ListNode *prev=meet;
ListNode *slow=head;
while(prev != slow)
{
prev=prev->next;
slow=slow->next;
}
return slow;
}
对于求带环链表的入口结点的证明过程可参考下图:
11.判断两个链表是否相交,如果相交则求出交点(假设链表不带环)
对这道面试题更加详细的介绍可参考我的这篇博客:
//判断链表是否相交,并求出交点(链表不带环)
bool CheckCross(ListNode *plist1,ListNode *plist2)
{
assert(plist1);
assert(plist2);
ListNode *p1=plist1;
ListNode *p2=plist2;
//p1,p2分别指向两条链表的尾结点
while(p1->next)
{
p1=p1->next;
}
while(p2->next)
{
p2=p2->next;
}
if(p1 == p2)
return true; //相交
else
return false; //不相交
}
ListNode *GetCrossEntryNode1(ListNode *plist1,ListNode *plist2)
{
assert(plist1);
assert(plist2);
ListNode *cur1=plist1;
ListNode *cur2=plist2;
int count1=0;
int count2=0;
//统计链表1和链表2的长度
while(cur1)
{
count1++;
cur1=cur1->next;
}
while(cur2)
{
count2++;
cur2=cur2->next;
}
cur1=plist1;
cur2=plist2;
//长链表先走count1-count2步,然后两个指针再一起前进
if(count1-count2 >= 0)
{
while(count1-count2)
{
cur1=cur1->next;
count1--;
}
while(cur1 != cur2)
{
cur1=cur1->next;
cur2=cur2->next;
}
}
else //count1 < count2
{
while(count2-count1)
{
cur2=cur2->next;
count2--;
}
while(cur1 != cur2)
{
cur1=cur1->next;
cur2=cur2->next;
}
}
return cur1;
}
ListNode *GetCrossEntryNode2(ListNode *plist1,ListNode *plist2)
{
assert(plist1);
assert(plist2);
ListNode *cur=plist1;
//构造环
while(cur->next)
{
cur=cur->next;
}
cur->next=plist2;
ListNode *meet=CheckCycle(plist1);
return GetCircleEntryNode(plist1,meet);
}
12.判断两个链表是否相交,如果相交则求出交点(链表可能带环)
对这道面试题更加详细的介绍可参考我的这篇博客:
//判断链表是否相交,并求出交点(链表可能带环)
bool CheckCrossCircle(ListNode *plist1,ListNode *plist2)
{
assert(plist1);
assert(plist2);
ListNode *meet1=CheckCycle(plist1);
ListNode *meet2=CheckCycle(plist2);
//如果有一个不带环则必然不相交
assert(meet1);
assert(meet2);
//两个链表都带环,如果相遇点在同一个环内则必然相交
ListNode *m1=meet1;
ListNode *m2=meet2;
while(m1 != m2)
{
m1=m1->next;
}
if(m1 == m2)
return true;
else
return false;
}
ListNode *GetCrossCircleNode(ListNode *plist1,ListNode *plist2)
{
assert(plist1);
assert(plist2);
ListNode *meet1=CheckCycle(plist1);
ListNode *meet2=CheckCycle(plist2);
assert(meet1);
assert(meet2);
ListNode *entry1=GetCircleEntryNode(plist1,meet1);
ListNode *entry2=GetCircleEntryNode(plist2,meet2);
if(entry1 == entry2) //交点在环外
{
//tail指向相遇点的第一个节点
//重新构造环
ListNode *tail=entry1;
while(tail->next != entry1)
{
tail=tail->next;
}
tail->next=plist1;
ListNode *meet=CheckCycle(plist2);
return GetCircleEntryNode(plist2,meet);
}
else //交点在环内
{
//交点就是entry1或者是entry2
return entry2;
}
}
13.单链表的排序
void BubbleSort(ListNode *plist)
{
assert(plist);
ListNode *cur=plist;
ListNode *tail=NULL;
bool flag=true;
while(cur != tail)
{
while(cur->next != tail)
{
if(cur->data > cur->next->data) //升序排序
{
flag=false;
int tmp=cur->data;
cur->data=cur->next->data;
cur->next->data=tmp;
}
cur=cur->next;
}
if(flag == true)
{
break;
}
tail=cur; //不断缩小比较的范围
cur=plist;
}
}
14.删除链表中重复的结点
题目描述:在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表:1 2 2 2 3 3 4 5 , 处理后为 1 4 5;
//删除链表中重复的结点
ListNode* DeleteDuplication(ListNode* head)
{
assert(head);
ListNode *prev=NULL;
ListNode *cur=head;
while(cur)
{
ListNode *next=cur->next;
int flag=1;
//当前结点的数据域和它的下一个结点的数据域相同则修改flag
if(next && cur->data == next->data)
{
flag=0;
}
//未修改,则继续遍历下一个结点
if(flag == 1)
{
prev=cur;
cur=cur->next;
}
//修改flag,则删除所有数据域相同的结点
else
{
int d=cur->data;
ListNode *del=cur;
while(del && del->data == d)
{
next=del->next;
delete del;
del=next;
}
if(prev == NULL)
head=next;
else
prev->next=next;
cur=next; //更新cur的值
}
}
return head;
}
关于对以上面试题的测试代码已经上传到github中,点击下列文字即可查看:
更详细的介绍可参考我的以下博客:
关于链表面试题的一些理解
链表面试题之带环问题
链表面试题之约瑟夫环问题
链表面试题之链表相交问题
在这里就分享结束了~~~