【算法】—— 链表常见算法1
1. 链表原地删除结点
1.1 问题描述
请编写一个函数,用于删除单链表中某个特定节点 。在设计函数时需要注意,你无法访问链表的头节点 head ,只能直接访问要被删除的节点 。
题目数据保证需要删除的节点不是末尾节点 。
示例
输入:head = [4,5,1,9], node = 5
输出:[4,1,9]
输入:head = [4,5,1,9], node = 1
输出:[4,5,9]
1.2 实现思路
由于参数中没有头结点,所以无法通过头结点遍历找到pos
结点的上一个,所以我们将pos
结点的下一个结点的值赋给pos
,删除pos
的下一个结点
1.3 代码实现
void deleteNode(struct ListNode* pos)
{
struct ListNode* posnext = pos->next;
pos->val = posnext->val;
pos->next = posnext->next;
free(posnext);
}
2. 链表原地插入结点
2.1 问题描述
设计一个函数,能够在特定结点前插入结点,无法访问链表头结点,只能访问该特定结点
题目保证该特定节点不是最后一个结点
2.2 实现思路
由于无法访问头结点,所以也不能遍历找到它的下一个节点,依然使用替换法将pos
结点和被插结点交换数值,将pos
结点作为被插结点插入原被插结点的后面
2.3 代码实现
//链表的结点声明
struct ListNode
{
int val;
struct ListNode* next;
}
void InsertNode(struct ListNode* pos, struct ListNode* newnode)
{
int temp = pos->val;
pos->val = newnode->val;
newnode->val = temp;
newnode->next = pos->next;
pos->next = newnode;
}
3. 移除链表元素
3.1 问题描述
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回新的头节点
示例:
3.2 实现思路
方法一(原地删除法)
遍历链表,将val
值的结点删除,为了方便删除,我们加入临时的头结点
代码实现
//链表的结点声明
struct ListNode
{
int val;
struct ListNode* next;
}
struct ListNode* removeElements(struct ListNode* head, int val)
{
if (head == NULL)
{
return head;
}
struct ListNode temp = {0};
temp.next = head;
struct ListNode* prev = &temp;
struct ListNode* cur = head;
while (cur != NULL)
{
if (cur->val == val)
{
prev->next = cur->next;
free(cur);
cur = prev->next;
}
else
{
prev = cur;
cur = cur->next;
}
}
head = temp.next;
return head;
}
方法二(重组链表法)
-
创建一个头指针,将原链表中值不为val的结点放在新头指针上
-
遍历链表,若是该值不为1,则将其插入到新链表上
- 链表遍历完毕将新链表返回即可
- 原链表在遍历过程中可以将其值为1的结点释放掉
代码实现
//链表的结点声明
struct ListNode
{
int val;
struct ListNode* next;
}
struct ListNode* removeElements(struct ListNode* head, int val)
{
if (head == NULL)
{
return head;
}
struct ListNode* newhead = NULL;
struct ListNode* tail = newhead;
struct ListNode* cur = head;
while (cur != NULL)
{
if (cur->val != val)
{
if (newhead == NULL)
{
newhead = cur;
tail = cur;
}
else
{
tail->next = cur;
tail = tail->next;
}
cur = cur->next;
}
else
{
head = cur->next;
free(cur);
cur = head;
}
}
if (tail != NULL)
{
tail->next = NULL;
}
return newhead;
}
4. 合并两个有序链表
4.1 问题描述
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
示例 2:
输入:l1 = [], l2 = [0]
输出:[0]
4.2 实现思路
以其中一个链表l1
作为最终返回链表,依次遍历,与另一条链表l2
的第一个结点比较,若是大于l1
当前结点,就将这个结点插入到l1
结点之前
- 在
l1
链表上创建一个头结点,方便插入
- 此时3大于2,将值为2的结点插入值为3的结点之前,然后继续遍历
- 继续遍历,将
l2
的值插入到l1
中
- 当
l1
中没有比l2
大的值时,将l2
剩余的链表插入到l1
的末尾,并将结果返回
4.3 代码实现
//链表的结点声明
struct ListNode
{
int val;
struct ListNode* next;
}
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
struct ListNode temp = {0};
temp.next = list1;
struct ListNode* prev = &temp;
struct ListNode* cur1 = list1;
struct ListNode* cur2 = list2;
while (cur1 != NULL && cur2 != NULL)
{
if (cur1->val > cur2->val)
{
list2 = cur2->next;
cur2->next = cur1;
prev->next = cur2;
prev = prev->next;
cur2 = list2;
}
else
{
prev = cur1;
cur1 = cur1->next;
}
}
if (list2 != NULL)
{
prev->next = list2;
}
list1 = temp.next;
return list1;
}
5. 反转单链表
5.1 问题描述
给你单链表的头节点head
,请你反转链表,并返回反转后的链表
示例
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
输入:head = [1,2]
输出:[2,1]
5.2 实现思路
方法一(重组新链表)
创建新头指针,遍历链表,依次将链表结点头插到新链表上
cur
结点的下一位指向newhead
,此时cur成为头指针
newhead
指向cur
,此时newhead
成为头指针
cur
继续遍历链表,next
记录下一个位置
- 返回新链表
代码实现
struct ListNode* reverseList(struct ListNode* head)
{
struct ListNode* newhead = NULL;
struct ListNode* cur = head;
struct ListNode* next = NULL;
while (cur != NULL)
{
next = cur->next;
cur->next = newhead;
newhead = cur;
cur = next;
}
return newhead;
}
方法二(反转链表指向)
依次遍历链表,将链表的指向依次反转过来
- 定义
prev
、cur
、next
指针分别指向前一个、当前、下一个结点
- 将当前结点的下一个指针指向前一个结点,完成反转
- 将三个结点依次向后移动一位
- 循环结束时当前结点为NULL,前一个结点为最后一个遍历的结点,也是反转后链表的第一个结点,将前一个结点返回
代码实现
struct ListNode* reverseList(struct ListNode* head)
{
struct ListNode* prev = NULL;
struct ListNode* cur = head;
struct ListNode* next = NULL;
while (cur != NULL)
{
next = cur->next;
cur->next = prev;
prev = cur;
cur = next;
}
return prev;
}