目录
题目二——876. 链表的中间结点 - 力扣(LeetCode)
题目四——203. 移除链表元素 - 力扣(LeetCode)
题目五——21. 合并两个有序链表 - 力扣(LeetCode)
题目十——142. 环形链表 II - 力扣(LeetCode)
题目十一——138. 随机链表的复制 - 力扣(LeetCode)
题目十三——24. 两两交换链表中的节点 - 力扣(LeetCode)
题目十四——143. 重排链表 - 力扣(LeetCode)
题目十五——23. 合并 K 个升序链表 - 力扣(LeetCode)
题目十六——25. K 个一组翻转链表 - 力扣(LeetCode)
链表算法题的常用技巧
1.画图:画图是最实用的
2.引入虚拟头节点:我们的算法题都是直接给你一个没有头节点的链表的,我们可以加上头节点,使得我们的算法过程更简单
3.不要吝啬空间,大胆去定义变量
我们来仔细讲一个例子;
在下面这个链表里面插入一个元素,我们通常就是按照下面这个图这么做的,而且具有强烈的先后顺序
但其实我们可以先定义一个next指针,让next 指针指向后面那个元素
这个时候我管你语句的执行顺序是什么,都是可以的。
4.快慢双指针
这个常常用于下面3种情况
- 判环
- 找链表中环的入口
- 找链表中倒数第n个结点
5.链表中的常用操作
- 1.创建一个新结点
- 2.尾插
- 3.头插(逆序链表题目等)
题目一——206. 反转链表 - 力扣(LeetCode)
想必这题,大家已经知道是使用前插的思想来解决的了。
如果大家不懂:可以去链表OJ1——反转链表(c语言实现)-优快云博客
那么我们很快就能写出下面这个代码
class Solution {
public:
struct ListNode* reverseList(struct ListNode* head)
{
// 如果链表为空或只有一个节点,则不需要反转,直接返回
if (head == NULL || head->next == NULL)
return head;
struct ListNode* next = head; // 初始化next指针,用于遍历原链表
head = NULL; // 新的头节点初始化为NULL,因为反转后原来的头节点会变成尾节点
// 遍历原链表的每个节点
while (next != NULL) {
struct ListNode* cur = next; // 当前节点
next = next->next; // 将next指针指向下一个节点,以便在循环中继续遍历
// 将当前节点的next指针指向新的头节点,实现链表的反转
cur->next = head;
// 更新头节点为当前节点,因为当前节点现在是新的头节点
head = cur;
}
// 返回反转后的链表的头节点
return head;
}
};
题目二——876. 链表的中间结点 - 力扣(LeetCode)
暴力解法我放在了:链表OJ2——链表的中间结点(c语言实现)-优快云博客
既然我们要找的是中间位置的结点,那么我们可以定义两个指针,第一个指针指向当前遍历到的最后一个结点,第二个指针时刻指向已经遍历过的结点的中间结点。如此进行下去,因为第二个指针始终指向的是已经遍历过的结点的中间结点,所以当链表遍历完后直接返回第二个指针即可。这就是所谓的“快慢指针”。
我们不妨定义两个指针名叫:fast,slow。
- fast:记录当前遍历到的最后一个结点。(快指针)
- slow:记录已经遍历过的结点的中间结点。(慢指针)
通过观察,我们可以发现,当slow指针走一步时,fast指针走一步或是走两步都满足slow指针指向的是已经遍历过的结点的中间结点。也就是slow指针走一步,fast指针最多可以走两步。
所以,我们可以遍历链表,当fast指针遍历到链表末尾时,就立刻返回此时的slow指针即可。
需要注意的是:因为fast指针一次是走两步,所以当fast指针指向的内容为空或是fast指针指向的结点所指向的内容为空时,均停止遍历链表。
这两种情况下均停止遍历,立刻返回slow指针。
这样,我们就在只遍历了一遍链表的情况下找到了中间结点的位置,即时间复杂度为O(n)。
class Solution {
public:
ListNode* middleNode(ListNode* head) {
// 定义两个指针,fast和slow,都从链表的头节点开始
ListNode* fast = head, *slow = head;
// 使用快慢指针技巧来找到链表的中间节点
// 当快指针fast没有到达链表末尾,并且快指针的下一个节点也不为NULL时,循环继续
while (fast != NULL && fast->next != NULL) {
// 慢指针slow每次向前移动一步
slow = slow->next;
// 快指针fast每次向前移动两步
fast = fast->next->next;
}
// 当循环结束时,慢指针slow指向的节点即为链表的中间节点
// 这是因为快指针fast的速度是慢指针slow的两倍,所以当快指针到达链表末尾时,慢指针恰好在链表的中间
return slow;
}
};
题目三——输出单向链表中倒数第k个结点_牛客题霸_牛客网
如果想看暴力解法的去: 链表OJ3——链表中倒数第k个结点-优快云博客
其实我们还是可以运用“快慢指针”的思想来解决这道题,这样可以使得代码的时间复杂度直接从O(n2)变为O(n) 。需要注意的是:这里所说的“快慢指针”并非一个指针走得快,另一个指针走得慢,而是快指针先走,慢指针在快指针走到某一位置后再开始走。
- 因为从最后一个结点开始,再往后走一步便是NULL;
- 从倒数第二个结点开始,再往后走两步便是NULL;
- 从倒数第k个结点开始,再往后走k步便是NULL。
所以我们可以先让快指针(fast)先走k步,然后慢指针(slow)再和快指针一起往后走,这样,当快指针走到NULL时,慢指针指向的结点就是倒数第k个结点。
我在这里先放一个模板(跟本题目没什么关系啊):
struct ListNode {
int val;
struct ListNode *next;
};
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k)
{
struct ListNode* fast = pListHead;//快指针
struct ListNode* slow = pListHead;//慢指针
while (k--)//快指针先向后移动k步
{
if (fast == NULL)//快指针移动过程中链表遍历结束,不存在倒数第k个结点
return NULL;
fast = fast->next;//快指针后移
}
while (fast)//快指针遍历完链表时结束遍历
{
fast = fast->next;//快指针后移
slow = slow->next;//慢指针后移
}
return slow;//返回慢指针的值
}
接着我们就来看看这个题目怎么做
有没有发现这道题目说是链表的倒数第K个元素,但是我们真的有必要去创建链表吗?没必要啊,我们完全就可以创建一个数组,找数组的倒数第K个元素。
#include <iostream>
#include<vector>
using namespace std;
int main() {
int n;
while (cin>>n) { // 注意 while 处理多个 case
vector<int> arr(n);//我们使用数组来充当链表
for(int x=0;x<n;x++)
{
cin>>arr[x];
}
int k;
cin>>k;
int fast=0;//快指针
while(k--&&fast<n)//快指针先走k步
{
fast++;
}
int slow=0;//慢指针
while(fast<n)//快慢指针一起走
{
slow++;
fast++;
}
cout<<arr[slow]<<endl;//此时慢指针指向的数就是中间结点
}
}
题目四——203. 移除链表元素 - 力扣(LeetCode)
这道题就是想考验我们删除链表元素的操作,我们可以使用三指针方法来解决,但是三指针方法有它的缺点——头结点需要特殊处理,我这里就不讲了,感兴趣的可以去:链表OJ4——删除链表中等于给定值 val 的所有节点(c语言实现)-优快云博客
我这里采用添加头结点的解法,这样子就不需要特殊处理了
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
//创建头结点
ListNode*guard=new ListNode();
guard->next = head;//让头结点指向链表的第一个结点
ListNode* cur = guard->next;//cur指针指向原链表第一个结点
ListNode* prev = guard;//prev指针指向头结点
while (cur != NULL)//当cur为空时,循环停止
{
if (cur->val == val)//当前排查的结点是待移除的结点
{
ListNode* next = cur->next;//记录待排查结点的后一个结点位置
prev->next = next;//prev指针指向的结点指向next
delete cur;//将cur指针指向的结点释放掉
cur = next;//将next指针赋值给cur指针
}
else//当前排查的结点不是待移除的结点
{
prev = cur;//指针后移
cur = cur->next;//指针后移
}
}
head = guard->next;//将头结点指向的位置赋值给头指针,使头指针指向链表第一个结点
delete guard;//释放头结点
guard = NULL;//及时置空
return head;//返回新的头指针
}
};