目录
反转链表
我们用取头节点依次进行头插的方式解决这道题。需要注意的是头插前要保存下一个节点。
struct ListNode* reverseList(struct ListNode* head){
typedef struct ListNode SL;
SL* cur = head;
SL* rhead = NULL;//初始指向空
while(cur)
{
SL* next = cur->next;
cur->next = rhead;
rhead = cur;
cur = next;
}
return rhead;
}
移除元素
思路一:删除第pos个位置的元素,也就是我们之前实现过的接口,需要考虑头删,以及是否传二级指针。
思路二:根据上面的示范图,大家脑子里有没有形成一幅顺序表尾插添加数据的图,我们也可以取满足条件的数据进行尾插,忽略指定元素,并保留它的下一个元素。
1.初始化指针
struct ListNode* removeElements(struct ListNode* head, int val){
typedef struct ListNode SLT;
SLT* newhead = NULL,*tail = NULL;//新链表头和尾
SLT* cur = head;//原链表遍历指针
2.尾插
while(cur)
{
if(cur->val!=val)//不等
{
if(tail == NULL)//第一次
{
newhead = tail = cur;
}
else
{
tail->next = cur;
tail = tail->next;
}
cur =cur->next;
else //相等
{
SLT* Next = cur->next;
free(cur);
cur = Next;
}
}
整体逻辑是没问题的,但是却过不了测试用例
我们具体问题具体分析,既然不知道哪里错了,就自己勤快一点,写一个简单的单链表,来进行调试,这种方式能很好的提高我们解决问题的能力和提升我们的代码水平。
#pragma once
#include<stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef struct ListNode
{
int val;
struct ListNode* next;
}SLT;
SLT* CreateSList(int* a, int n)//用数组构建链表
{
SLT* phead = nullptr, * ptail = nullptr;
int x = 0;
for (int i = 0; i < n; i++)
{
//sacnf("%d",&x);
SLT* newnode = (SLT*)malloc(sizeof(SLT));//初始化(BuyList())
if (newnode == nullptr)
{
perror("malloc fail");
exit(-1);
}
else
{
newnode->val = a[i];
newnode->next = nullptr;
}
if (phead == nullptr)//连结
{
phead = ptail = newnode;
}
else
{
ptail->next = newnode;
ptail = newnode;
}
}
return phead;
}
struct ListNode* removeElements(SLT* head, int val) {
SLT* newhead = nullptr, * tail = nullptr;//新链表头和尾
SLT* cur = head;//原链表遍历指针
while (cur)
{
if (cur->val != val)//不等
{
if (tail == NULL)//第一次
{
newhead = tail = cur;
}
else
{
tail->next = cur;
tail = tail->next;
}
cur = cur->next;
}
else //相等
{
SLT* Next = cur->next;
free(cur);
cur = Next;
}
}
return newhead;
}
int main()
{
int a[] = { 1,2,6,3,4,5,6};
SLT* plist = CreateSList(a, sizeof(a) / sizeof(int));
removeElements(plist, 6);
return 0;
}
大家能看出什么错误吗,没错,可以看到程序正常删除了我们想要的数据,但是最后一个数据没有指向空,而是指向了要删除的6的地址。
解决了这个问题后更换力扣上的测试用例再调试,发现还有错:
这次的数组是每个都相同且均是删除对象,那这样就会导致tail指向空,发生解引用问题,所以我们再加上判断,这样就完美了。
struct ListNode* removeElements(SLT* head, int val) {
typedef struct ListNode SLT;//最好不要这样定义
SLT* newhead = nullptr, * tail = nullptr;//新链表头和尾
SLT* cur = head;//原链表遍历指针
while (cur)
{
if (cur->val != val)//不等
{
if (tail == NULL)//第一次
{
newhead = tail = cur;
}
else
{
tail->next = cur;
tail = tail->next;
}
cur = cur->next;
}
else //相等
{
SLT* Next = cur->next;
free(cur);
cur = Next;
}
}
if(tail)
tail->next = nullptr;
return newhead;
}
大家可以自己去实现一下我刚才的单链表调试函数,以后做题时遇到这种提醒就可以套模板调试,提升效率~。
这道题还可以添加哨兵节点做,这就有个好处:尾插进新链表时不用担心链表为空且省去了第一次尾插判空情况。
struct ListNode* removeElements(struct ListNode* head, int val){
typedef struct ListNode SLT;
SLT* guard,*tail;
guard = tail = (SLT*)malloc(sizeof(SLT));
SLT* cur = head;
while(cur)
{
if(cur->val!=val)
{
tail->next = cur;
tail = tail->next;
cur =cur->next;
}
else
{
SLT* Next = cur->next;
free(cur);
cur = Next;
}
}
tail->next = NULL;//避免删除最后一个数据前一个数据指向野指针
SLT* newhead = guard->next;
free(guard);
return newhead;
}
注意返回时没有明确说明不能返回guard,而是返回它的下一个节点,也就是存储有效数据的节点。就算传递的是空指针也能有效返回。大家可以自行感受。
合并有序链表
思路:取小的尾插,其中一方结束就将剩余数据链接到新数组中去(有序)。
这里为了简单我们用哨兵节点实现。
上面的测试用例表明如果其中一方为空或者都为空我们可以不用比较,直接返回他们的地址,这样就能完美通过了。
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
/*if(list1 == NULL)//非哨兵位
{
return list2;
}
if(list2 == NULL)
{
return list1;
}*/
struct ListNode* guard,*tail;
guard = tail = (struct ListNode*)malloc(sizeof(struct ListNode));
while(list1 && list2)
{
if(list1->val < list2->val)
{
tail->next = list1;
tail = tail->next;
list1 = list1->next;
}
else
{
tail->next = list2;
tail = tail->next;
list2 = list2->next;
}
}
if(list1)//剩余链接
tail->next = list1;
if(list2)
tail->next = list2;
struct ListNode* newhead = guard->next;
free(guard);
return newhead;
}
与上道题不同的是,这道题不用担心结尾节点野指针的问题。可以看到哨兵位十分方便。
总结一下:
- 带哨兵位可以不用传递二级指针(只改变结构体)
- 尾插用很方便
- 单链表不常用