学完了单链表之后,我们对其基本结构已经有了一定的了解,接下来我们通过一些题目强化对链表的理解,同时学习一些面试笔试题目的新思路以及加强对数据结构单链表的掌握。
目录
题目一.876. 链表的中间结点 - 力扣(LeetCode)
题目二:21. 合并两个有序链表 - 力扣(LeetCode)
题目三:203. 移除链表元素 - 力扣(LeetCode)
题目四: 206. 反转链表 - 力扣(LeetCode)
题目五:141. 环形链表 - 力扣(LeetCode)
题目六: 142. 环形链表 II - 力扣(LeetCode)
head
,请你找出并返回链表的中间结点。我们一起来思考一下这道题目:返回链表的中间结点。我们先来了解一种新思路:快慢指针!我们定义两个指针:一个快指针,一个慢指针。每次快指针走两步,慢指针走一步。我们来看一下演示过程:
一个中间结点情况:
两个中间结点情况:
通过演示过程我们可以清楚的看到:
总结以上规律,应在当 fast遇到或越过尾节点时跳出循环,并返回 slow
即可。
struct ListNode* middleNode(struct ListNode* head) {
struct ListNode* slow=head,*fast=head;
while(fast&&fast->next)
{
slow=slow->next;
fast=fast->next->next;
}
return slow;
}
我们要返回一个升序的新链表,那么我们可以借助一个头指针,将list1和list2进行比较,值小的尾插放入新的链表中,再用头指针指向新的链表就可以。
在题目中注意判断list1为空,list2为空是怎么办?
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
struct ListNode*head=NULL,*tail=NULL;
// 处理特殊情况
if(list1==NULL)
return list2;
if(list2==NULL)
return list1;
while(list1&&list2)
{
if(list1->valval)
{
if(tail==NULL)
tail=head=list1;
else
{
tail->next=list1;
tail=tail->next;
}
list1=list1->next;
}
else
{
if(tail==NULL)
tail=head=list2;
else
{
tail->next=list2;
tail=tail->next;
}
list2=list2->next;
}
}
if(list1)
tail->next=list1;
if(list2)
tail->next=list2;
return head;
}
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
思路一:我们定义一个指针,让这个指针移动从而去寻找链表中和val相等的值,要是遇到就把它释放掉,然后把上一个结点和释放掉后的下一个结点连接起来,最后返回头结点head就可以。但这个思路有什么问题我们想一想?如果头结点就需要释放呢,那我们就要把返回的头结点此时后移,注意考虑这个情况!
大家可以自己思考一下,再参考下面代码。
struct ListNode* removeElements(struct ListNode* head, int val) {
struct ListNode*prev=NULL,*cur=head;
while(cur)
{
if(cur->val==val)
{
if(cur==head)
{
head=cur->next;
free(cur);
cur=head;//cur=head->next错误
}
else
{
prev->next=cur->next;
free(cur);
cur=prev->next;
}
}
else
{
prev=cur;
cur=cur->next;
}
}
return head;
}
思路二:我们定义一个新的结点,当指针指向的值不等于val值的时候,把结点连接到新结点上。这样返回的新的头结点就是一个没有val值的链表。
大家可以自己思考一下,再参考下面代码。
struct ListNode* removeElements(struct ListNode* head, int val) {
struct ListNode* cur=head;
struct ListNode* tail=NULL,*newnode=NULL;
while(cur)
{
if(cur->val==val)
{
struct ListNode* node=cur;
cur=cur->next;
free(node);
}
else
{
if(tail==NULL)
{
newnode=tail=cur;
}
else
{
tail->next=cur;
tail=tail->next;
}
cur=cur->next;
}
}
if(tail)
tail->next=NULL;
return newnode;
}
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
还记得之前写的线性表头插吗?我们发现头插和输入的顺序刚好相反,那我们把这个思想用在这道题目上,我们把链表的值头插到新的一个链表中,返回头插后的链表的头结点。
struct ListNode* reverseList(struct ListNode* head) {
struct ListNode* cur=head;
struct ListNode* newhead=NULL;
while(cur)
{
struct ListNode* next=cur->next;
cur->next=newhead;
newhead=cur;
cur=next;
}
return newhead;
}
head
,判断链表中是否有环。next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos
不作为参数进行传递 。仅仅是为了标识链表的实际情况。true
。 否则,返回 false
。这个题目超级有意思,检查链表里有没有环,还记得我们的快慢指针吗?如果有环的话,快指针肯定先入环,慢指针如果在环中和快指针相遇了,说明有环;如果没有相遇,说明无环。
大家肯定有一个问题:为什么有环就一定可以追上,我们一起分析一下。当slow进环后,fast开始追击,假设它们之间的距离为N,那么走一次,它们之间的距离变为N-1,下一次为N-2,N-3,......3,2,1,0,最后它们之间的距离就是0。所以只要有环,它们一定会相遇。
bool hasCycle(struct ListNode *head) {
struct ListNode* slow=head,*fast=head;
while(fast&&fast->next)//快指针将到达链表尾部,该链表不为环形链表
{
slow=slow->next;
fast=fast->next->next;
if(slow==fast)
return true;
}
return false;
}
head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。这个题目其实有点数学题的意思,我们来分析一下:假设起点到入口点的距离是L,环的周长是C,入口点到相遇点距离是X,我们已经分析过,slow进环后一圈内,fast必追上slow,那么slow的距离就是:L+X,fast的距离是L+n*C+X,L可能很长,导致fast已经在环里走路不止一圈,slow才进入,所以fast是n*C,那么我们已知fast走的距离是slow的两倍,这是快慢指针的定义,所以我们列出式子:2(L+X)=L+n*C+X,解出L=(n-1)*C+C-X.也就是说,一个指针从起点走,另一个从相遇点走,他们会在入口点相遇。
struct ListNode *detectCycle(struct ListNode *head) {
struct ListNode* slow,*fast;
slow=fast=head;
while(fast&&fast->next)
{
slow=slow->next;
fast=fast->next->next;
if(slow==fast)
{
struct ListNode* meet=slow;
while(head!=meet)
{
meet=meet->next;
head=head->next;
}
return meet;
}
}
return NULL;
}
今天的分享就到这里,大家有哪里不懂的可以私信我,或在评论区讨论,大家可以把这些题目作为链表的练习题目,希望对大家的编程和数据结构有帮助!