题目描述
思路1: 从头遍历链表,遇到数据域为val的节点删除,链接其前一个节点和后一个节点
特殊情况讨论:
1️⃣空链表
如示例2:无论val的值是多少,都返回空链表
2️⃣头节点的数据值等于val
此时需要更改头节点,所以参数应当设置为二级指针,但是本题当中函数的参数及返回类型已给定,要求返回新的头节点,所以如果更改头节点则更新head,返回新的链表头即可,这也是避免使用二级指针的一种方法,下文还会讲解另一种避免使用二级指针方法:使用带哨兵头的链表
3️⃣尾节点的数据值等于val
对于尾节点的数据值等于val的情况,其实和非头节点的的处理情况一样,当删除最后一个节点时,prev->next = cur->next ==NULL,因此删除节点可以分为头节点删除和非头节点删除
以下为参考代码:
struct ListNode* removeElements(struct ListNode* head, int val){
//非空链表
struct ListNode* cur = head;
struct ListNode* prev = head;
while(cur)
{
//删除节点
if(cur->val == val)
{
//头删
if(cur == head)
{
head = head->next;
free(cur);
cur = head;
}
//非头删
else
{
prev->next = cur->next;//链接
free(cur);//释放
cur = prev->next;
}
}
else
{
prev = cur;
cur = cur->next;
}
}
return head;//返回链表头
}
思路二:定义一个新链表,将不是数据值val的节点尾插到新链表
如上图,迭代直到cur == NULL
📖Note
1️⃣这里的尾插指的是整个节点的插入,cur指向要插入的节点,和单链表中尾插同理,cur相当于我们新建立的一个节点,将其插入newhead指向的链表即可
2️⃣newhead要分空链表和非空链表两种情况讨论,具体步骤参考单链表尾插操作
3️⃣原链表每个节点判断或尾插后,要进行空间释放,否则会造成内存泄漏问题
4️⃣尾插结束后,tail指向新链表的最后一个节点,需要将tail->next置空
struct ListNode* removeElements(struct ListNode* head, int val)
{
struct ListNode* newhead = NULL;
struct ListNode* cur = head;
struct ListNode* tail = newhead;
while (cur)
{
if (cur->val != val)
{
//第一个节点插入
if (tail == NULL)
{
newhead = tail = cur;
}
else
{
tail->next = cur;
tail = tail->next;//更新tail
}
cur = cur->next;
}
else
{
struct ListNode* del = cur;
cur = cur->next;
free(del);
}
}
if (tail)
{
tail->next = NULL;
}
return newhead;
}
以下介绍一种特殊的链表结构:带哨兵头的链表
以下为带哨兵头和不带哨兵头链表的对比
带哨兵头的链表:即给链表的头节点之前增加一个节点,这个节点的数据域不存储任何有效数据,其指针域存放的是指向链表头节点的指针head
带哨兵头节点链表的优势:
1️⃣对于单链表的操作,我们每次都需要分空链表和非空链表两种情况讨论,但当存在带哨兵头链表这种结构后,我们就不需要考虑空链表的情况
对于空链表,其带哨兵头后结构如下:
2️⃣除此之外,一些需要更改链表头节点的操作,如头插头删等,不需要我们给函数传二级指针,因为此时修改的是guard->next,即修改的是结构体中的成员变量,我们只需要结构指针guard即可
以下我们使用带哨兵头节点的链表实现移除链表元素:
我们定义一个带哨兵头节点的新链表newhead,带哨兵头之后,newhead尾插就不需要分空链表和非空链表两种情况,直接尾插即可
📖Note
- 需要创建一个新的节点为guard,guard不能定义为NULL,否则就会发生空指针的访问
- 函数的返回值为guard->next,即指向链表头节点的指针
struct ListNode* removeElements(struct ListNode* head, int val)
{
struct ListNode* guard = (struct ListNode*)malloc(sizeof(struct ListNode));
struct ListNode* tail = guard;
struct ListNode* cur = head;
while (cur)
{
if (cur->val != val)
{
tail->next = cur;
tail = tail->next;//更新tail
cur = cur->next;
}
else
{
struct ListNode* del = cur;
cur = cur->next;
free(del);
}
}
tail->next = cur;//最后一个节点等于val等情况
return guard->next;
}
如下合并两个有序链表,返回新的升序链表,使用带哨兵头节点的链表更方便
题目分析:
对于这两个升序链表,它们都满足前一个节点一定小于后一个节点,所以只需要依次比较这两个链表中的值,将较小的值尾插到新链表,直到所有元素比较结束,返回新链表的头节点指针即可,以样例一为例,具体步骤如下:
📖Note
- 新链表的尾插需要考虑第一个节点尾插的情况,但当新链表带有哨兵头节点时,不需要分情况讨论
- 两个节点的数据比较,取较小值尾插到新链表,当两个节点中的数据相等时,我们先将L1中的数据尾插,判断条件为(cur1->val) <= (cur2->val),尾插cur1指向的节点
- 当L1和L2有一个遍历结束,即cur1==NULL 或cur2==NULL 成立时,不再进行比较,将tail链接到非空指针所指向的位置即可
参考代码如下:
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
struct ListNode* cur1 = list1;
struct ListNode* cur2 = list2;
struct ListNode* guard = (struct ListNode*)malloc(sizeof(struct ListNode));
guard->next = NULL;
struct ListNode* tail = guard;
while (cur1 && cur2)
{
//cur1尾插
if (cur1->val <= cur2->val)
{
tail->next = cur1;
cur1 = cur1->next;//更新cur1
}
//cur2尾插
else
{
tail->next = cur2;
cur2 = cur2->next;//更新cur2
}
tail = tail->next;//更新tail
}
//cur1和cur2中有一个为空
if (cur1)
{
tail->next = cur1;
}
if (cur2)
{
tail->next = cur2;
}
return guard->next;
}