通过这6道初级链表题检验你的链表知识是否合格
Posted 捕获一只小肚皮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了通过这6道初级链表题检验你的链表知识是否合格相关的知识,希望对你有一定的参考价值。
前言
学习完毕链表(单双链表)后,博主更新了6道比较有意思的链表题,并且全部配以图解文字说明,对于大家有一定的帮助,谢谢支持哦~
01-删除链表元素
给你一个链表的头节点
head
和一个整数val
,请你删除链表中所有满足Node.val == val
的节点,并返回 新的头节点。示例:1
输入:head = [1,2,6,3,4,5,6], val = 6 输出:[1,2,3,4,5]
示例:2
输入:head = [], val = 1 输出:[]
示例:3
输入:head = [7,7,7,7], val = 7 输出:[]
迭代法(这种操作和我们学习链表增删查改时候一样):
比较明显,这是一个链表的增删改查里面的删除操作
.大家还记得在单链表
里面我们要删除一个结点的步骤是什么吗? 没错:
- 第一步: 找到需要删除的结点前一个结点,即如果
cur->next->val
等于val
,说明cur
就是需要删除结点的前一个结点 - 第二步: 让需要删除结点的前一个结点 与 需要删除结点的后一个结点 连接,即
cur->next = cur->next->next
- 第三步: 释放需要删除的结点.(博主知道不释放内存是一个很糟糕的习惯,但是这里只是在讲解算法题,所以博主就偷懒了)
而大致的迭达方法如下:
如果 cur
的下一个节点的节点值不等于给定的 val
,则保留下一个节点,将 val
移动到下一个节点即可。
当 cur
的下一个节点为空时,链表遍历结束,此时所有节点值等于val
的节点都被删除
但是我们还记得吗?在单链表的头删操作中,我们是分了情况进行讨论的,即只有一个结点和多个结点时候,这就会比较麻烦,有没有什么好的操作省去它呢? 有,这个操作就是给原来链表增加一个哑结点
示意图(假设有下面一个链表,需要删除的val是6,绿色移动点代表在判断是否等于val,橙色代表val结点后面一个结点):
题解:
struct ListNode* removeElements(struct ListNode* head, int val)
{
struct ListNode* dummynode = (struct ListNode*)malloc(sizeof(struct ListNode)); //创建哑结点
dummynode->next = head; //连接链表
struct ListNode* cur = dummynode; //迭代结点
while(cur->next) //当下一个为空指针就结束
{
if(cur->next->val == val) //如果下一个结点的值等于val,就连接橙色结点
{
cur->next = cur->next->next;
}
else
{
cur = cur->next;
}
}
return dummynode->next;//返回修改后的结点(不要哑结点哦~~)
}
02-反转链表
给你单链表的头节点
head
,请你反转链表,并返回反转后的链表示例1:
输入:head = [1,2,3,4,5] 输出:[5,4,3,2,1]
示例2:
输入:head = [1,2] 输出:[2,1]
示例3:
输入:head = [] 输出:[]
我们的正常想法是,先给一个prev指针置为NULL,再给个cur指针赋值head,然后cur指向prev,最后prev和cur依次像下图这样迭代:
有问题吗?问题很大,问题出在哪里了?那就是当cur指向prev后,等下一次迭代时,cur就找不到他原来后面的结点,所以说,我们还差一个指针用来保存下一个结点,上代码:
struct ListNode* reverseList(struct ListNode* head)
{
struct ListNode* prev = NULL;
struct ListNode* cur = head;
struct ListNode* next= NULL;
while (cur)
{
next = cur->next;
cur->next = prev;
prev = cur;
cur = next;
}
return prev;
}
03-查找链表中间结点
给定一个头结点为
head
的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
示例1:
输入:[1,2,3,4,5] 输出:此列表中的结点 3 (序列化形式:[3,4,5])
示例2:
输入:[1,2,3,4,5,6] 输出:此列表中的结点 4 (序列化形式:[4,5,6])
博主这里介绍的是快慢指针法则,即快指针比慢指针多走一步,这样当快指针走完整个链表时候,慢指针就刚好是中间结点.
但有个情况是快指针的结束条件受到结点的奇偶
影响.
下面我们分开进行演示:
- 当结点数是偶数时候,发现
fast
为空:
- 当结点数是奇数时候,发现
fast
为尾结点,即fast->next等于NULL
:
代码:
struct ListNode* middleNode(struct ListNode* head)
{
struct ListNode* fast,*slow; //定义快慢指针
fast = slow = head;
while(fast && (fast->next)) // 当fast为空,或者fast->next为空,就结束循环,说明此时slow已经走到了中间结点
{
fast = fast->next->next;//fast走两步
slow = slow->next; //slow走一步
}
return slow;
}
04-链表倒数第K个结点
实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。
示例1:
输入: 1->2->3->4->5 和 k = 2 输出: 4
博主这里介绍的还是快慢指针法,但是这里就需要一个变化了~~~,什么变化呢?大家想想上一个题目的快慢指针的差异是什么?
没错,上个题的差异是fast的速度是slow的两倍,而我们这个题目怎么弄呢?没错,可以先让fast走K步,然后slow与fast速度一样
当fast
走到末尾时候结束(fast->next等于NULL)
如图:
代码:
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k )
{
struct ListNode* slow,*fast;
slow = fast = pListHead; //创建快慢指针
while(k--) //fast先走K步
{
if(fast) //注意!!!!这里必须要判断,为什么呢?因为题目并没有规定k是小于等于链表长度的哦
fast = fast->next;
else
return NULL;
}
while(fast) //同步
{
fast = fast->next;
slow = slow->next;
}
return slow;
}
05-合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例1
输入:l1 = [1,2,4], l2 = [1,3,4] 输出:[1,1,2,3,4,4]
示例2
输入:l1 = [], l2 = [] 输出:[]
示例3
输入:l1 = [], l2 = [0] 输出:[0]
如果这个题是两个数组,大家有没有啥想法? 对,估计大部分的想法都是 新建立一个数组,然后数组上下两两比较,放进新数组.
而我们链表其实也可以这样,我们可以建立一个哑结点
,然后让链表1和链表2每个结点进行比较,小的就依次放在哑结点后,如图:
代码:
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
struct ListNode* p1 = l1,*p2 = l2;
struct ListNode* newhead = NULL,*tail = NULL;
p1 == NULL ? (return p2):(;); //后面括号是空语句
p2 == NULL ? (return p1):(;);
newhead = tail = (struct ListNode* )malloc(sizeof(struct ListNode)); //创建新结点
while(p1 && p2) //开始一一比较并进行连接
{
(p1-> val) <= (p2->val) ? //比较谁小
(tail->next = p1,p1 = p1->next,tail = tail->next) : //p1的值更小,则连接p1,然后tail和p1迭代
(tail->next = p2,p2 = p2->next,tail = tail->next); //p2的值更小,则连接p2,然后tail和p2迭代
}
p1 ? (tail->next = p1):(p1); //如果p1还有值,则把剩下的p1连接
p2 ? (tail->next = p2):(p2); //如果p2还有值,则把剩下的p2连接
struct ListNode* pp = newhead->next; //保存哑结点后面的结点
free(newhead); //释放哑结点
newhead = NULL;
return pp;
}
06-链表分割
现有一链表,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。
思路:
同时创建两个哑结点,值小于x的结点放在第一个结点后,值大于等于x的结点放在第二个结点后,等原链表放完后,再把第二个哑结点链表后面的数据连接在第一个哑结点链表上,具体过程如图:
代码:
class Partition {
public:
ListNode* partition(ListNode* pHead, int x)
{
if(pHead == NULL) return NULL; //如果原链表是空,就返回空
ListNode* cur = pHead,*dummyone,*dummytwo,*tailone,*tailtwo,;
tailone = dummyone = (ListNode*)malloc(sizeof(ListNode));//创建哑结点
tailtwo = dummytwo = (ListNode*)malloc(sizeof(ListNode));
while(cur) //进行迭代
{
cur->val < x ?
(tailone->next = cur,tailone = tailone->next,cur = cur->next) : //小值放在dummyone链表
(tailtwo->next = cur,tailtwo = tailtwo->next,cur = cur->next) ; //大值放在dummytwo链表
}
tailone->next = dummytwo->next;//连接
tailtwo->next = NULL;
return dummyone->next;
}
};
以上是关于通过这6道初级链表题检验你的链表知识是否合格的主要内容,如果未能解决你的问题,请参考以下文章