代码随想录算法训练营第三天
LeetCode 203. 移除链表元素
题目链接:203. 移除链表元素
文章讲解:代码随想录#203. 移除链表元素
视频讲解:手把手带你学会操作链表 | LeetCode:203.移除链表元素
题目描述
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
示例1
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
示例2
输入:head = [], val = 1
输出:[]
示例3
输入:head = [7,7,7,7], val = 7
输出:[]
思路
对于原链表的删除,需要区分删除头节点与删除其他节点,这两者的删除方式不同的。
- 删除非头节点时,
就是将要删除的节点的前一个节点指向要删除的节点的下一个节点,如图(图片借用代码随想录)所示:
伪代码如下
// 假如当前遍历到cur节点,想要删除cur后面的节点
struct ListNode *cur = head;
if (cur != NULL && cur->next != NULL) {
struct ListNode *tmp = cur->next; // 将要删除的节点存入临时变量,方便后面释放内存
cur->next = cur->next->next; // 当前节点指向下下一个节点
free(tmp);
}
总之,要删除链表的非头节点都是通过前一个节点来删除当前节点的,但头结点没有前一个节点。
- 删除头节点时,
需要将头节点移动到下一个节点就可以。
伪代码如下
// 删除头节点head
if (head != NULL) {
struct ListNode *tmp = head; // 将要删除的头节点存入临时变量,方便后面释放内存
head = head->next; // 当头节点指向下一个节点
free(tmp);
删除头节点和删除非头节点的区别在于,删除非头节点时是通过cur来删除cur->next,而删除头节点时是直接将head指向下一个节点,仔细观察可知,这两块代码的处理逻辑是相同。
所以可以构造一个虚拟节点指向头节点head,这样就可以按照统一的方式删除节点了,具体可以参考下面代码。
参考代码
struct ListNode* removeElements(struct ListNode* head, int val) {
struct ListNode* dummynode = (struct ListNode*)malloc(sizeof(struct ListNode));
dummynode->next = head;
struct ListNode* cur = dummynode; // 构建个虚拟节点指向head
while (cur->next != NULL) {
if (cur->next->val == val) { // 可以通过当前节点找到下一个节点
struct ListNode* tmp = cur->next; // 保存下一个节点
cur->next = cur->next->next; // 指向下下一个节点
free(tmp); // 释放下一个节点,不能释放当前节点,否则就找不到下一个节点了
} else {
cur = cur->next;
}
}
head = dummynode->next;
free(dummynode);
return head;
}
总结
- 不会就画图,模拟链表删除
- 需要先将要删除的节点存入临时变量中,然后移动节点,再释放该节点
- 一定要注意cur=head还是cur=head->next,以及各种循环的判断条件
LeetCode 707.设计链表
题目链接:707.设计链表
文章讲解:代码随想录#707.设计链表
视频讲解:帮你把链表操作学个通透!LeetCode:707.设计链表
题目描述
你可以选择使用单链表或者双链表,设计并实现自己的链表。
单链表中的节点应该具备两个属性:val 和 next 。val 是当前节点的值,next 是指向下一个节点的指针/引用。
如果是双向链表,则还需要属性 prev 以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。
实现 MyLinkedList 类:
- MyLinkedList() 初始化 MyLinkedList 对象。
- int get(int index) 获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1 。
- void addAtHead(int val) 将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。
- void addAtTail(int val) 将一个值为 val 的节点追加到链表中作为链表的最后一个元素。
- void addAtIndex(int index, int val) 将一个值为 val 的节点插入到链表中下标为 index 的节点之前。如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将 不会插入 到链表中。
- void deleteAtIndex(int index) 如果下标有效,则删除链表中下标为 index 的节点。
示例1
输入
[“MyLinkedList”, “addAtHead”, “addAtTail”, “addAtIndex”, “get”, “deleteAtIndex”, “get”]
[[], [1], [3], [1, 2], [1], [1], [1]]
输出
[null, null, null, null, 2, null, 3]
解释
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addAtHead(1);
myLinkedList.addAtTail(3);
myLinkedList.addAtIndex(1, 2); // 链表变为 1->2->3
myLinkedList.get(1); // 返回 2
myLinkedList.deleteAtIndex(1); // 现在,链表变为 1->3
myLinkedList.get(1); // 返回 3
思路
参考视频或者文章讲解,主要就是考查链表的基本操作
参考代码
typedef struct {
int val;
struct ListNode *next;
}MyLinkedList;
MyLinkedList* myLinkedListCreate() {
MyLinkedList* head = (MyLinkedList*)malloc(sizeof(MyLinkedList));
head->next = NULL;
return head;
}
int myLinkedListGet(MyLinkedList* obj, int index) {
if (index < 0 || obj == NULL) {
return -1; // 异常情况需要考虑
}
MyLinkedList *cur = obj->next; // index=0,取非头节点的第一个节点
for (int i = 0; cur != NULL; i++){
if (i == index){
return cur->val;
}
else{
cur = cur->next;
}
}
return -1;
}
void myLinkedListAddAtHead(MyLinkedList* obj, int val) {
if (obj == NULL) {
return; // 异常情况需要考虑
}
MyLinkedList* node = (MyLinkedList*)malloc(sizeof(MyLinkedList));
node->val = val;
node->next = obj->next; // 新节点指向非头节点的第一个节点
obj->next = node; // 头节点指向新节点
}
void myLinkedListAddAtTail(MyLinkedList* obj, int val) {
if (obj == NULL) {
return; // 异常情况需要考虑
}
MyLinkedList* tmp = obj;
MyLinkedList* node = (MyLinkedList*)malloc(sizeof(MyLinkedList));
node->val = val;
node->next = NULL;
while (tmp->next != NULL) {
tmp = tmp->next; // 找到最后一个节点
}
tmp->next = node; // 最后一个节点指向新节点
}
void myLinkedListAddAtIndex(MyLinkedList* obj, int index, int val) {
if (index < 0 || obj == NULL) {
return; // 异常情况需要考虑
}
if (index == 0) {
myLinkedListAddAtHead(obj, val);
return;
}
MyLinkedList* tmp = obj->next; // 指向非头节点的第一个节点
for (int i = 1; tmp != NULL; i++) { // 退出条件:遍历完链表
if (i == index) { // 如果index存在,则在它后面插入新的节点
MyLinkedList* node = (MyLinkedList*)malloc(sizeof(MyLinkedList));
node->val = val;
node->next = tmp->next;
tmp->next = node;
return;
} else {
tmp = tmp->next; // 继续遍历
}
}
}
void myLinkedListDeleteAtIndex(MyLinkedList* obj, int index) {
if (index < 0 || obj == NULL) {
return; // 异常情况需要考虑
}
if (index == 0) {
MyLinkedList* tmp = obj->next;
if (tmp != NULL) {
obj->next = tmp->next;
free(tmp);
}
return;
}
MyLinkedList* cur = obj->next;
for (int i = 1; cur != NULL && cur->next != NULL; i++) {
if (i == index) {
MyLinkedList* tmp = cur->next;
if (tmp != NULL) {
cur->next = tmp->next;
free(tmp);
}
return;
} else {
cur = cur->next;
}
}
}
void myLinkedListFree(MyLinkedList* obj) {
if (obj == NULL) {
return;
}
while(obj != NULL){
MyLinkedList *tmp = obj;
obj = obj->next;
free(tmp);
}
}
总结
- 这几个接口函数覆盖了链表的基本操作,需要多加练习。
- index为0时,比较疑惑,到底是指向头节点呢,还是第一个节点呢,需要确认。
LeetCode 206. 反转链表
题目链接:206. 反转链表
文章讲解:代码随想录#206. 反转链表
视频讲解:帮你拿下反转链表 | LeetCode:206.反转链表 | 双指针法 | 递归法
题目描述
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例1
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例2
输入:head = [1,2]
输出:[2,1]
思路
算是比较简单的一道题,用双指针进行遍历,最重要的要保存tmp指针用来保存下一个节点。
使用两个指针pre和cur,初始化时将pre指向NULL,cur指向第一个节点。
从前到后开始遍历cur指针,每遍历一次cur时,将cur的下一个节点先保存到tmp变量中,
然后将cur指向pre实现指针的反转,接着移动pre指向cur,移动cur指向tmp…
直到cur为NULL时,说明整个链表已经遍历完了,此时pre指向原来链表的最后一个节点,所以返回pre即可。
可以查看代码随想录的动画,比较生动形象。
参考代码
struct ListNode* reverseList(struct ListNode* head) {
struct ListNode *pre = NULL; // 双指针 pre 和 cur
struct ListNode *cur = head;
struct ListNode *tmp = NULL; // tmp用来向后遍历
while (cur) {
tmp = cur->next;
cur->next = pre; // 反转
pre = cur; // pre、cur向后遍历
cur = tmp;
}
return pre;
}
总结
这道题也可以使用递归来实现,代码如下:
struct ListNode* reverseList(struct ListNode* pre, struct ListNode* cur) {
if (cur == NULL) {
return pre; // 递归函数一定要有边界条件,当遍历到cur为NULL时,说明pre已经指向了原链表的最后一个节点了
}
struct ListNode* next = cur->next; // 向后遍历
cur->next = pre; // 反转
return reverseList(cur, next);