前言
这次博客将要以图解的形式,把单链表的经典题目,讲解,绝对是干货,来吧兄弟萌
第一题
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
示例 1:
输入:head = [1,2,6,3,4,5,6], val = 6 输出:[1,2,3,4,5]
题目链接
203. 移除链表元素 - 力扣(LeetCode)
直白的思路
这个题目完全可以无脑解决,看嘛,我们可以直接再重新构建一个链表,把不等于val值的结点
尾插起来不就好了嘛
好吧这个也让大家看看代码,我们以上面例子为例
这个代码有点长
#include<stdio.h>
#include<stdlib.h>
struct SListNode {int val;struct SListNode* next;
};
//创建节点
struct SListNode* Buynewnode(int input)
{struct SListNode* newnode = (struct SListNode*)malloc(sizeof(struct SListNode));newnode->next = NULL;newnode->val = input;return newnode;
}
int main()
{//1->2->6->3->4->5->6//构建链表struct SListNode* head = Buynewnode(1);head->next = Buynewnode(2);head->next->next = Buynewnode(6);head->next->next->next=Buynewnode(3);struct SListNode* head4 = head->next->next->next;head4->next=Buynewnode(4);head4->next->next= Buynewnode(5);head4->next->next->next=Buynewnode(6);int val = 6;//遍历链表struct SListNode* newtail = NULL;struct SListNode* newhead = NULL;while (head){if (head->val!= val){if (newtail == NULL){newtail=newhead = Buynewnode(head->val);}else{newtail->next = Buynewnode(head->val);newtail = newtail->next;}}head = head->next;}while (newhead){printf("%d ", newhead->val);newhead = newhead->next;}return 0;
}
看看结果
是不是对上了,当然我们这里是自己实现的,所以代码量就多了些
改进思路:双指针+哨兵位节点
我们可不可以不构建,在原有的链表上改变呢
可以
通过前后指针可以抵达 前后中三个节点,如果要删除一个结点就把中间的结点删除
让前节点连接后节点
看图吧
ok
再次看看代码
struct ListNode* removeElements(struct ListNode* head, int val) {struct ListNode*phead=(struct ListNode*)malloc(sizeof(struct ListNode));phead->next=head;struct ListNode*cur=head;struct ListNode*prev=phead;while(cur){if(cur->val==val){struct ListNode*temp=cur->next;free(cur);cur=temp;prev->next=cur;}else{prev=cur;cur=cur->next;}}return phead->next;
}
这里直接用一个函数,来表示好了,主要优化空间复杂
第二题
反转链表
看看题目吧
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5] 输出:[5,4,3,2,1]
206. 反转链表 - 力扣(LeetCode)
有没有想到暴力的解法呢
其实就是我们仍然可以重新构建一个链表,遍历旧的链表,在用头插的方式,刚好反转了链表对吧
1->2->3->4->5
第一次头插1 第二次头插 2->1 第三次头插 3->2->1 第四次头插 4->3->2->1 第五次头插
5->4->3->2->1
对吧
看代码就好了,代码以上面为例子
#include<stdio.h>
#include<stdlib.h>
struct SListNode {int val;struct SListNode* next;
};
struct SListNode* Buynewnode(int input)
{struct SListNode* newnode = (struct SListNode*)malloc(sizeof(struct SListNode));newnode->next = NULL;newnode->val = input;return newnode;
}
int main()
{//构建链表struct SListNode* head = Buynewnode(1);head->next = Buynewnode(2);head->next->next = Buynewnode(3);head->next->next->next = Buynewnode(4);head->next->next->next->next = Buynewnode(5);//遍历链表,并且头插struct SListNode* newhead = NULL;while (head){if (newhead == NULL){newhead = Buynewnode(head->val);}else{struct SListNode* newnode = Buynewnode(head->val);newnode->next = newhead;newhead = newnode;}head = head->next;}while (newhead){printf("%d ", newhead->val);newhead = newhead->next;}return 0;
}
这里的时间复杂度还是o(n)但是多了空间复杂(N)
我们来看看,优化方案
双指针法
思路
第一个指针叫做 prev=NULL;第二个指针叫做 cur=head;
首先一个指针在最左侧此时为NULL 另一个指针在链表第一个元素
首先记录第二个指针的next temp=cur->next;
再让第二个指针指向第一个指针之后 cur->next=prev;
第一个指针继承第二个指针的值,第二个指针等于它的next prev=next;cur=cur->next;
本质的思路就是让他们的指向相反,同时就完成了反转,红色部分为核心代码
当然,整个反转链表是一个循环,结束条件就是cur为null时,因为在它为空前
它把最后一个节点个反转了
但是要注意一点,最后我们的头结点就是prev了
看代码吧
注意这里的代码是没有将自定义函数包含,当然,看了前面的,应该就可以理解
int main()
{//构建链表struct SListNode* head = Buynewnode(1);head->next = Buynewnode(2);head->next->next = Buynewnode(3);head->next->next->next = Buynewnode(4);head->next->next->next->next = Buynewnode(5);//遍历链表,并且头插struct SListNode* prev = NULL;struct SListNode* cur = head;while (cur){//构建临时变量的原因是:cur一旦改变方向就无法找到下一个,所以记录下一个struct SListNode* temp = cur->next;cur->next = prev;prev = cur;cur = temp;}while (prev){printf("%d ", prev->val);prev = prev->next;}return 0;
}
我们看看结果吧
OK
看第三题
第三题
给你单链表的头结点 head
,请你找出并返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
示例 1:
输入:head = [1,2,3,4,5] 输出:[3,4,5] 解释:链表只有一个中间结点,值为 3
示例 2:
输入:head = [1,2,3,4,5,6] 输出:[4,5,6] 解释:该链表有两个中间结点,值分别为 3 和 4 ,返回第二个结点。
提示:
- 链表的结点数范围是
[1, 100]
1 <= Node.val <= 100
这个题目更偏向于理解,怎么说呢其实有两种情况
一种是链表长度是奇数,另一种链表长度是偶数
奇数中间节点在中间
偶数的话在偏右边
刚好对应两例子中的两种请况
主要的思路为快慢指针
还是画图吧
当然,这里仍然是要循环 结束条件有两个,一个是fast==NULL 另一个是fast->next==NULL
ok看代码吧
int main()
{//构造链表struct SListNode* head = Buynewnode(1);head->next = Buynewnode(2);head->next->next = Buynewnode(3);head->next->next->next = Buynewnode(4);head->next->next->next->next = Buynewnode(5);struct SListNode* slow = head;struct SListNode* fast = head;while (fast && fast->next){slow = slow->next;fast = fast->next->next;}printf("%d ", slow->val);return 0;
}
OK,如果理解,其实没有太大的问题
第四题
实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值
注意:本题相对原题稍作改动
示例:
输入: 1->2->3->4->5 和 k = 2 输出: 4
说明:
给定的 k 保证是有效的
暴力思路
其实暴力思路也非常简单
我们要返回的值是int类型的值,我们完全可以把整个链表遍历一遍
把它的值存入一个数组中,然后根据k返回一个值不就行了吗?
当然这里的链表的长度不确定,所以还需要动态开辟数组
思路简单,代码也很简单
void checkmemory(int* arr, int size, int capacity)
{if (size == capacity){int* temp=(int *)realloc(arr, sizeof(capacity * 2));if (temp!= NULL){arr = temp;capacity *= 2;}}
}
int main()
{//创建数组struct SListNode* head = Buynewnode(1);head->next = Buynewnode(2);head->next->next = Buynewnode(3);head->next->next->next = Buynewnode(4);head->next->next->next->next = Buynewnode(5);int k = 2;int* arr=(int *)malloc(sizeof(int)*3);int size = 0;int capacity = 3;while (head){checkmemory(arr,size,capacity);arr[size++] = head->val;head = head->next;}printf("%d ", arr[size - k]);return 0;
}
这里仍然是空间复杂度高了
继续看优化吧
此次优化的思路来自于数学
相对位置
他不是要找倒数第k和元素吗
说明倒数第k个元素,与最后一个元素的下一个,也就是空指针相差了k个元素
比如
1->2->3->4->5->6->null
k=2,也就是5刚好与null相差2
那么反过来想,先让一个front指针走k步
然后让第二个back指针,与front指针一次走一步
那么当front指针走到NULL时,back指针与他相差k步
刚好就是倒数第k个元素
OK
这样解释应该非常清楚
看代码
//返回倒数第k个节点
int main()
{//创建数组struct SListNode* head = Buynewnode(1);head->next = Buynewnode(2);head->next->next = Buynewnode(3);head->next->next->next = Buynewnode(4);head->next->next->next->next = Buynewnode(5);int k = 2;struct SListNode* front=head;struct SListNode* back=head;while (k--){front = front->next;}while (front){front = front->next;back = back->next;}printf("%d ", back->val);return 0;
}
这样,就可以解决问题
总结
今天就写到这里吧,四题虽然少但是思路确是很好
希望有所帮助