补day3,依旧C语言,因为链表掌握并不是很熟练,所以基本都是先看课学,然后进行总结,节省时间。
一、移除链表
1、力扣题目
题目描述:给你一个链表的头节点
head
和一个整数val
,请你删除链表中所有满足Node.val == val
的节点,并返回 新的头节点 。示例:
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
2、视频学习
视频链接:手把手带你学会操作链表 | LeetCode:203.移除链表元素_哔哩哔哩_bilibili
主要有两个思路:
(1):分成头节点和普通节点进行处理
(2):由(1)衍生出来的,增加一个虚拟头节点使得都能够按照普通节点进行处理
第一个思路注意事项:cursor要等于head,如果cursor是head->next,那么无法找到前面的节点。
代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeElements(struct ListNode* head, int val) {
struct ListNode* temp;
while(head&&head->val==val){
temp=head;
head=head->next;
free(temp);
}//头节点处理
struct ListNode* cursor=head;
//cursor必须为head是因为,如果是head->next,那么会找不到前面的节点,cursor也会丢失。
while(cursor!=NULL &&cursor->next!=NULL){
//第一个条件是为防止head为空,第二个条件是为了防止处理节点为空
if(cursor->next->val==val){
struct ListNode* temp;
cursor->next=cursor->next->next;
free(temp);
}
else{
cursor=cursor->next;
}
}
return head;
}
第二个思路:增加一个头节点
代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeElements(struct ListNode* head, int val) {
//创建一个虚拟头节点,并初始化
struct ListNode *shead=(struct ListNode *)malloc(sizeof(struct ListNode));
shead->next=head;
struct ListNode* cursor=shead;
while(cursor->next!=NULL&&cursor!=NULL){
if(cursor->next->val==val){
struct ListNode* temp;
temp=cursor->next;
cursor->next=cursor->next->next;
free(temp);//释放该节点
}
else{
cursor=cursor->next;
}
}//循环处理
head=shead->next;
free(shead);//释放虚拟头
return head;
}
3、总结
①掌握两种思想,本质都是考虑对首部的处理。最好优先考虑使用第二个思路,即增加虚拟头节点,这样简化了后续的处理。
②在用C/C++来写的时候要注意释放,有的语言系统会自动释放,C/C++要手动释放。
③第一个思路,如果条件为while(cursor->next!=NULL),那么前面要先处理head为空的情况,可以加上一句if(head==null)return head;
④代码中可以增加typedef struct ListNode ListNode;语句来进行重命名(将struct ListNode重命名为ListNode),方便书写和阅读,不然就像上述代码一样每次都要struct ListNode。
二、设计链表
1、力扣题目
题目描述:
你可以选择使用单链表或者双链表,设计并实现自己的链表。
单链表中的节点应该具备两个属性:
val
和next
。val
是当前节点的值,next
是指向下一个节点的指针/引用。如果是双向链表,则还需要属性
prev
以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。实现
MyLinkedList
类:
1、MyLinkedList()
初始化MyLinkedList
对象。
2、int get(int index)
获取链表中下标为index
的节点的值。如果下标无效,则返回-1
。
3、void addAtHead(int val)
将一个值为val
的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。
4、void addAtTail(int val)
将一个值为val
的节点追加到链表中作为链表的最后一个元素。
5、void addAtIndex(int index, int val)
将一个值为val
的节点插入到链表中下标为index
的节点之前。如果index
等于链表的长度,那么该节点会被追加到链表的末尾。如果index
比长度更大,该节点将 不会插入 到链表中。
6、void deleteAtIndex(int index)
如果下标有效,则删除链表中下标为index
的节点。示例:
输入 ["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
该题主要是考察对链表的一些操作,加深对链表的认识。不过需要注意到,用C写的时候obj为头节点,index=0的时候代表的是首节点而非obj,两个概念不一样,需要注意区分
2、视频学习
视频链接:帮你把链表操作学个通透!LeetCode:707.设计链表_哔哩哔哩_bilibili
代码如下:
typedef struct {
int val;
struct MyLinkedList *next;
} MyLinkedList;
MyLinkedList* myLinkedListCreate() {
MyLinkedList *List=(MyLinkedList *)malloc(sizeof(MyLinkedList));
List->next=NULL;
return List;
}
int myLinkedListGet(MyLinkedList* obj, int index) {
int i=0;
MyLinkedList *search=obj->next;//头节点没有下标
while(search!=NULL){//循环,当前节点不为空时
if(i==index){
return search->val;//当前下标=index,返回目标值
}
else{
search=search->next;//search指针向后移动
}
i++;
}
free(search);
return -1;//异常处理
}
void myLinkedListAddAtHead(MyLinkedList* obj, int val) {
//创建新的节点
MyLinkedList *node=(MyLinkedList *)malloc(sizeof(MyLinkedList));
node->val=val;
//添加节点
node->next=obj->next;
obj->next=node;
}
void myLinkedListAddAtTail(MyLinkedList* obj, int val) {
//创建新的节点
MyLinkedList *node=(MyLinkedList *)malloc(sizeof(MyLinkedList));
node->val=val;
node->next=NULL;
//寻找尾部节点
MyLinkedList *search=obj;
while(search->next!=NULL){
search=search->next;
}
//添加到尾部
search->next=node;
//不能free(research);因为初始化search时为obj,直接释放会出错。
}
void myLinkedListAddAtIndex(MyLinkedList* obj, int index, int val) {
//创建节点
MyLinkedList *node=(MyLinkedList *)malloc(sizeof(MyLinkedList));
node->val=val;
//在index为头节点时
if(index==0){
node->next=obj->next;
obj->next=node;//更新头节点
}
//可以直接调用创建首元素函数
//if (index == 0){
// myLinkedListAddAtHead(obj, val);
// return;
// }
MyLinkedList *search=obj->next;
int i=1;
while(search!=NULL){
if(i==index){//在其他节点添加
node->next=search->next;//注意是next
search->next=node;
return;
}
else{
search=search->next;
}
i++;
}
free(search);
}
void myLinkedListDeleteAtIndex(MyLinkedList* obj, int index) {
if (index == 0){//删除0号元素需要改动头,所以单独处理
MyLinkedList *tmp = obj->next;
if (tmp != NULL){
obj->next = tmp->next;
free(tmp);
}
return;
}
MyLinkedList *search = obj->next;
int i=1;//search在下标为0那里
while(search!=NULL){
if(i==index){//当前元素要被删除
MyLinkedList *tmp=search->next;
if(tmp!=NULL){
search->next=tmp->next;
free(tmp);
}
}
else{
search=search->next;//后移
}
i++;
}
free(search);
}
void myLinkedListFree(MyLinkedList* obj) {
while(obj!=NULL){
MyLinkedList *temp=obj;
obj=obj->next;
free(temp);
}
}
3、总结
①要注意区分头节点和首节点,头节点是虚拟节点,注意index指的是哪一个节点
②在对链表进行操作时,要注意避免空指针问题,对某一节点操作时需注意前后节点
③注意对首节点的考虑,首节点的改动可能会影响头节点的next,需单独考虑
④养成free()的习惯,但是在free时需要注意能否free,如上述代码中myLinkedListAddAtTail函数便不能free(search)。
三、反转链表
1、力扣题目
题目描述:给你单链表的头节点
head
,请你反转链表,并返回反转后的链表。示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
2、视频学习
视频链接:帮你拿下反转链表 | LeetCode:206.反转链表 | 双指针法 | 递归法_哔哩哔哩_bilibili
(1)双指针法
该方法需注意,初始时pre=null,用临时指针保存cur的下一个位置,防止丢失。
代码如下:
struct ListNode* reverseList(struct ListNode* head) {
typedef struct ListNode ListNode;
ListNode *cur=head;
ListNode *pre=NULL;
while(cur!=NULL){
ListNode *tmp=cur->next;//临时指针保存
cur->next=pre;//换方向
pre=cur;
cur=tmp;
}
return pre;//注意返回的是pre
}
(2)递归调用
递归调用其实本质思想跟双指针的解法差不多,只不过cur和pre进行了递归调用来实现while循环。
不过需要注意递归调用时cur和pre的变化
struct ListNode* reverse(struct ListNode *cur, struct ListNode *pre) {
if (cur == NULL) return pre; //终止条件,返回pre
struct ListNode *tmp;
tmp= cur->next;
cur->next = pre;
return reverse(tmp, cur); // 递归调用,当前cur和pre分别为tmp,原先的cur
}
struct ListNode* reverseList(struct ListNode* head) {
return reverse(head, NULL);
}
3、总结
该题主要是掌握双指针的思想,递归不过是双指针的一种变形。