链表是面试最常问到的代码,虽然每个代码不长,但是逻辑能力和代码能力却能很好的体现出来。
我们就来看一下链表面试题都有什么吧!
1. 比较顺序表和链表的优缺点,说说它们分别在什么场景下使用?
2. 从尾到头打印单链表
3. 删除一个无头单链表的非尾节点(不能遍历链表)
4. 在无头单链表的一个非头节点前插入一个节点
5. 单链表实现约瑟夫环(JosephCircle)
6. 逆置/反转单链表
7. 单链表排序(冒泡排序&快速排序)
8. 合并两个有序链表,合并后依然有序
9. 查找单链表的中间节点,要求只能遍历一次链表
10. 查找单链表的倒数第k个节点,要求只能遍历一次链表
11. 删除链表的倒数第K个结点
12. 判断单链表是否带环?若带环,求环的长度?求环的入口点?并计算每个算法的时间复
杂度&空间复杂度。
13. 判断两个链表是否相交,若相交,求交点。(假设链表不带环)
14. 判断两个链表是否相交,若相交,求交点。(假设链表可能带环)【升级版】
15. 复杂链表的复制。一个链表的每个节点,有一个指向next指针指向下一个节点,还有一
个random指针指向这个链表中的一个随机节点或者NULL,现在要求实现复制这个链表,
返回复制后的新链表。
16. 求两个已排序单链表中相同的数据。void UnionSet(Node* l1, Node* l2);
解决方法
(1)比较顺序表和链表的优缺点,说说它们分别在什么场景下使用?
- 从结构来看:顺序表的存储空间是连续的,在内存中需要一块完整的空间来构造顺序表;链表的存储空间在逻辑上是连续的,但是在物理地址上可以连续也可以不连续,在内存中比较好构造空间,也较好的利用零碎空间,但是如果不注意,会引起内存碎片的问题。
- 从功能上看:顺序表支持随机访问,在访问某一元素时效率较高,适合查找和随机存取操作。顺序表的插入和删除操作来讲,需要移动元素,时间复杂度为O(n),不适合进行插入和删除操作。链表不支持随机访问操作,链表的插入和删除操作不需要移动结点,直接插入即可时间复杂度为O(1),效率较高。
- 从CPU的缓存效率来看:顺序表的CPU高速缓存效率较高(因为空间连续),链表的CPU高速缓存效率较低(空间物理地址不连续)。
在看其他题目之前,先看一下链表的结点结构和一些插入删除操作
typedef struct ListNode
{
int data;
ListNode* pNext;
}Node,*pNode;
void pushBack(ListNode*& phead,int data)
{
Node* pcur=phead;
Node* newNode =new ListNode();
newNode->data=data;
if(phead==NULL)
{
phead=newNode;
phead->pNext=NULL;
}
else
{
while(pcur->pNext!=NULL)
{
pcur=pcur->pNext;
}
pcur->pNext=newNode;
newNode->pNext=NULL;
}
}
void pushHead(ListNode*& phead,int data)
{
Node* newNode=new ListNode();
newNode->data=data;
newNode->pNext=phead;
phead=newNode;
}
void popBack(ListNode*&phead)
{
if(phead==NULL)
return;
if(phead->pNext==NULL)
{
delete phead;
phead=NULL;
}
ListNode* pDel=phead;
while(pDel->pNext->pNext!=NULL)
{
pDel=pDel->pNext;
}
delete pDel->pNext;
pDel->pNext=NULL;
}
void popHead(ListNode*&phead)
{
if(phead==NULL)
return;
ListNode* pDel=phead;
phead=pDel->pNext;
delete pDel;
pDel=NULL;
}
void InitList(ListNode*&phead)
{
phead=NULL;
}
(2)可用递归方式或者借助栈
递归方式
void PrintListReverseRecur(ListNode* phead)
{
if(phead!=NULL)
{
if(phead->pNext!=NULL)
PrintListReverseRecur(phead->pNext);
cout<<phead->data;
}
}
栈方式
void PrintListReverse(ListNode* phead)
{
stack<pNode> s;
if(phead==NULL)
return;
pNode pcur=phead;
while(pcur!=NULL)
{
s.push(pcur);
pcur=pcur->pNext;
}
while(!s.empty())
{
pNode print=s.top();
cout<<print->data;
s.pop();
}
}
(3)用后一个结点的值代替当前结点的值,删除后一个结点
void DelNoHeadTailNode(ListNode* phead,ListNode* pcur)
{
if(phead==NULL||phead->pNext==NULL)
return;
ListNode*pDel=pcur->next;
pcur->data=pDel->data;
pcur->pNext=pDel->pNext;
delete pDel;
}
(4)先在当前结点的后面插入一个新结点,用新结点的值代替当前结点,用当前结点的值替换新结点的值
void InsertNoheadFront(ListNode*pos,int data)
{
assert(pos);
ListNode* newNode=new ListNode;
newNode->pNext=pos->pNext;
pos->pNext=newNode;
newNode->data=pos->data;
pos->data=data;
}
(5)实现约瑟夫环
int LastRemaining(size_t n,size_t m)
{
if(n<1||m<1)
return -1;
size_t i=0;
list<int> nums;
for(i=0;i<n;i++)
{
nums.push_back(i);
}
list<int>::iterator current=nums.begin();
while(nums.size()>1)
{
for(int i=1;i<m;i++)
{
current++;
if(current==nums.end())
current=nums.begin();
}
list<int>::iterator next=++current;
if(next==nums.end())
next=nums.begin();
--current;
nums.erase(current);
current=next;
}
return (*current);
}
int LastReNum(size_t n,size_t m)
{
if(n<1||m<1)
return -1;
int last=0;
for(int i=2;i<=n;i++)
{
last=(last+m)%i;
}
return last;
}
(6)三指针法实现链表逆置
ListNode* ReverseList(ListNode* phead)
{
if(phead==NULL||phead->pNext==NULL)
return;
ListNode* pre=NULL;
ListNode* pcur=phead;
ListNode* pnext=NULL;
ListNode* ReverHead=NULL;
while(pcur!=NULL)
{
pnext=pcur->pNext;
if(pnext==NULL)
ReverHead=pcur;
pcur->pNext=pre;
pre=pcur;
pcur=pnext;
}
return ReverHead;
}
(7)冒泡排序单链表
void BubbleSortList(ListNode* phead)
361 {
362 if(phead==NULL||phead->pNext==NULL)
363 return;
364 ListNode* pcur=phead;
365 ListNode* pnext=NULL;
366 bool flag=false;
367 while(pcur->pNext!=NULL)
368 {
369 ListNode* pagain=pcur;
370 for(pnext=pagain->pNext;pnext!=NULL;pnext=pnext->pNext)
371 {
372 int temp=pagain->data;
373 if(temp>pnext->data)
374 {
375 pagain->data=pnext->data;
376 pnext->data=temp;
377 flag=true;
378 pagain=pnext;
379
380 }
381 if(flag==false)
382 return head;
}
384 pcur=pcur->pNext;
385 }
386 }
(8)递归合并,大事化小
ListNode* MergeList(ListNode* phead1,ListNode*phead2)
76 {
77 if(phead1==NULL)
78 return phead2;
79 else if(phead2==NULL)
80 return phead1;
81
82 ListNode* MergeNode=NULL;
83 ListNode* pcur1=phead1;
84 ListNode* pcur2=phead2;
85 if(pcur1->data<pcur2->data)
86 {
87 MergeNode=pcur1;
88 MergeNode->pNext=MergeList(pcur1->pNext,pcur2);
89 }
90 else
91 {
92 MergeNode=pcur2;
93 MergeNode->pNext=MergeList(pcur1,pcur2->pNext);
94 }
95 return MergeNode;
96 }
(9)快慢指针法查找中间结点
ListNode* MiddleNode(ListNode* phead)
{
ListNode* pFast=phead;
ListNode* pSlow=phead;
while(pFast!=NULL&&pFast->pNext!=NULL)
{
pFast=pFast->pNext-pNext;
pSlow=pSlow->pNext;
}
return pSlow;
}
(10)先后两个指针法查找倒数第K个结点
ListNode* FindKthToTail(ListNode* phead,size_t k)
109 {
110 if(phead==NULL||k==0)
111 return NULL;
112
113 ListNode* pAhead=phead;
114 ListNode* pBehind=phead;
115 for(size_t i=0;i<k-1;i++)
116 {
117 if(pAhead->pNext!=NULL)
118 pAhead=pAhead->pNext;
119 else
120 return NULL;
121 }
122 while(pAhead->pNext!=NULL)
123 {
124 pAhead=pAhead->pNext;
125 pBehind=pBehind->pNext;
126 }
127 return pBehind;
128 }
(10) 删除倒数第K个结点:两指针法,第一个指针先走K步,然后两个指针一起走,第二个指针的next就是要删除的指针。
void DelKthtoTail(ListNode* phead,size_t k)
130 {
131 if(phead==NULL||k==0)
132 return;
133 ListNode* pFirst=phead;
134 ListNode* pSecond=phead;
135 for(size_t i=0;i<k;i++)
136 {
137 if(pFirst->pNext!=NULL)
138 pFirst=pFirst->pNext;
139 else
140 return NULL;
141 }
142 while(pFirst->pNext!=NULL)
143 {
144 pFirst=pFirst->pNext;
145 pSecond=pSecond->pNext;
146 }
147 ListNode* pDel=pSecond->pNext;
148 pSecond->pNext=pDel->pNext;
149 delete pDel;
150
151 }
(11)快慢指针法,快指针每次走两步,慢指针每次走一步,如果带环,一定会相遇,返回相遇结点;如果不带环,则快指针走到NULL位置结束。
ListNode* MeetingNode(ListNode* phead)
153 {
154 if(phead==NULL)
155 return;
156 ListNode* fast=phead;
157 ListNode* slow=phead;
158 while(fast!=NULL&&slow!=NULL)
159 {
160 if(fast==slow)
161 return fast;
162 slow=slow->pNext;
163 fast=fast->pNext;
164
165 if(fast!=NULL)
166 fast=fast->pNext;
167 }
168 return NULL;
169 }
如果带环,利用相遇结点来求出环中结点的个数
int GetCicleLen(ListNode* phead)
{
ListNode* meetingNode=MeetingNode(phead);
173 if(meetingNode==NULL)
174 return NULL;
175 //Nodes of num
176 int nodesLoop=1;
177 ListNode* pcur=meetingNode;
178 while(pcur->pNext!=meetingNode)
179 {
180 pcur=pcur->pNext;
181 ++nodesLoop;
182 }
return nodesLoop;
}
如果带环,先求出环中结点的个数,让第一个指针先走nodesLoop步,然后两指针一起走,直到相遇,就是环的入口点。
ListNode* EntryNode(ListNode* phead)
171 {
172 ListNode* meetingNode=MeetingNode(phead);
173 if(meetingNode==NULL)
174 return NULL;
175 //Nodes of num
176 int nodesLoop=1;
177 ListNode* pcur=meetingNode;
178 while(pcur->pNext!=meetingNode)
179 {
180 pcur=pcur->pNext;
181 ++nodesLoop;
182 }
183 //entry
184 pcur=phead;
185 for(int i=0;i<nodesLoop;i++)
186 {
187 pcur=pcur->pNext;
188 }
189 ListNode* pcur2=phead;
190 while(pcur!=pcur2)
191 {
pcur=pcur->pNext;
193 pcur2=pcur2->pNext;
194 }
195 return pcur;
196 }
(13)不带环:如果两个链表相交,则最后一个结点一定是一样的。
bool Isintersect(ListNode* phead1,ListNode* phead2)
210 {
211 if(phead1==NULL||phead2==NULL)
212 return false;
213 while(phead1->pNext!=NULL)
214 {
215 phead1=phead1->pNext;
216 }
217 while(phead2->pNext!=NULL)
218 {
219 phead2=phead2->pNext;
220 }
221 if(phead1==phead2)
222 return true;
223 else
224 return false;
225
226 }
如果相交,则第一个交点一定是:求出两个链表的长度,求出两长度之差Div,让长链表先走Div步,然后两指针一起走,直到相遇。
int GetListLength(ListNode* phead)
198 {
199 if(phead==NULL)
200 return 0;
201 int length=0;
202 while(phead!=NULL)
203 {
204 length++;
205 phead=phead->pNext;
206 }
207 return length;
208 }
ListNode* FindFirstNode(ListNode* phead1,ListNode* phead2)
292 {
293 if(Isintersect(phead1,phead2))
294 {
295 int length1=GetListLength(phead1);
296 int length2=GetListLength(phead2);
297
298 int Dif=length1-length2;
299 ListNode* Long=phead1;
300 ListNode* Short=phead2;
301 if(length2>length1)
302 {
303 Dif=length2-length1;
304 ListNode* Long=phead2;
305 ListNode* Short=phead1;
306 }
307 for(int i=0;i<Dif;i++)
308 Long=Long->pNext;
309 while(Long!=NULL&&Short!=NULL&&Long!=Short)
310 {
Long=Long->pNext;
312 Short=Short->pNext;
313 }
314 ListNode* pFirstCommonNode=Long;
315 return pFirstCommonNode;
316 }
317 }
(14)两链表可能带环:最终归为只有两个链表同时带环时才可能相交
bool IsintersectCross(ListNode* phead1,ListNode* phead2)
228 {
229 ListNode* meetNode1=MeetingNode(phead1); //判断是否带环
230 ListNode* meetNode2=MeetingNode(phead2);
231 if(meetNode1==NULL&&meetNode2==NULL)
232 return Isintersect(phead1,phead2); //如果两个都不带环则是上一个题的判断方式
233 else if(meetNode1&&meetNode2) //如果两个都带环,判断是否相交
234 {
235 int n=GetCircleLen(phead1); //求环的长度
236 while(n--)
237 {
238 if(meetNode1==meetNode2)
239 return true;
240
241 meetNode1=meetNode1->pNext;
242 }
243 return false;
244 }
245 else //一个带环一个不带环不相交
246 return false;
247 }
如果两带环链表相交,则分别求出两链表的入口点,求出头到入口点的距离,求出差的距离。让长链表先走差距离步,然后两个链表一起走,直到相遇。
ListNode* MeetingNodeCircle(ListNode* phead1,ListNode* phead2)
249 {
250 ListNode* pcur1=EntryNode(phead1);
251 ListNode* pcur2=EntryNode(phead2);
252 ListNode* pH1=phead1;
253 ListNode* pH2=phead2;
254 int len1=1;
255 int len2=1;
256 int SubLen=0;
257 if(pcur1!=pcur2)
258 return pcur1;
259 while(pH1!=pcur1)
260 {
261 len1++;
262 pH1=pH1->pNext;
263 }
264 while(pH2!=pcur2)
265 {
266 len2++;
267 pH2=pH2->pNext;
268 }
273 SubLen=len1-len2;
274 ListNode* pLong=phead1;
275 ListNode* pShort=phead2;
276 if(len1<len2)
277 {
278 SubLen=len2-len1;
279 ListNode* pLong=phead2;
280 ListNode* pShort=phead1;
281 }
282 for(int i=0;i<SubLen;i++)
283 pLong=pLong->pNext;
284 while(pLong!=NULL&&pShort!=NULL&&pLong!=pShort)
285 {
286 pLong=pLong->pNext;
287 pShort=pShort->pNext;
288 }
289 return pLong;
(15) 复杂链表的复制
复杂链表的复制
(16)对于两个链表,分别从头开始比较,如果第一个链表的数据大于第二个链表,则第二个链表向后走;反之,则第一个链表向后走,如果相等,则开辟新结点保存
ListNode UnionSet(ListNode* pL1, ListNode* pL2)
{
ListNode* PSList1 = pL1;
ListNode* PSList2 = pL2;
ListNode* pNewHead = NULL;
ListNode* pNode = NULL;
//每次比较两个链表头指针指向的数据是否相等,不相等,就让数据小的头指针后移,相等,则把该数据保存起来,
//两个头指针同时后移,直到其中一个指向空为止
while ((NULL != PSList1) || (NULL != PSList2))
{
if (PSList1->data > PSList2->data)
{
PSList2 = PSList2->pNextNode;
}
else if (PSList1->data < PSList2->data)
{
PSList1 = PSList1->pNextNode;
}
//用一个新的链表来保存两个链表中相同的数据
else
{
ListNode* newNode=new ListNode;
newNode->data=PSList1->data;
newNode->pNext=pNode;
pNode=newNode;
}
}
return pNode;
}