链表理论基础
一、c语言链表节点定义
typedef struct ListNodeT {
int val;
struct ListNodeT next;
} ListNode;
二、其他
1.术语
singly-linked list 单链表
LinkList 链表
ListNode 链表节点
2.注意事项
①删除节点,需要释放(free)节点!
203.移除链表元素
一、做题感受&第一想法
思路较为清晰。(单独处理头结点)
但是实现起来漏了一种情况(当该节点不是val也不是头结点时,直接让p和pre指向后一个节点),导致一开始超出时间限制,因为跳不出循环。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeElements(struct ListNode* head, int val) {
struct ListNode* p = head, *pre = head;
while(p != NULL){
if( p == head && p->val == val ){
p = p->next;
free(head);
head = p;
pre = p;
}
else if( p == head ){
p = p->next;
}
else if( p != head && p->val == val ){
pre -> next = p -> next;
free(p);
p = pre -> next;
}
else if( p != head && p->val != val){ //一开始漏掉了这种情况
p = p -> next;
pre = pre -> next;
}
}
return head;
}
二、学习文章后收获
1.删除单链表节点总体思路两种:
(1)单独处理头结点
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
// 删除头结点
while (head != NULL && head->val == val) { // 注意这里不是if
ListNode* tmp = head;
head = head->next;
delete tmp;
}
// 删除非头结点
ListNode* cur = head;
while (cur != NULL && cur->next!= NULL) {
if (cur->next->val == val) {
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
} else {
cur = cur->next;
}
}
return head;
}
};
用一个while循环单独处理头结点,直到头结点不等于val。
之后,用cur表示当前节点,检查cur->next是否等于val。如果相等则删除节点,否则cur向后移动检查后续节点。
(2)创建虚拟头结点
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dummyHead = new ListNode(0); // 设置一个虚拟头结点
dummyHead->next = head; // 将虚拟头结点指向head,这样方便后面做删除操作
ListNode* cur = dummyHead;
while (cur->next != NULL) {
if(cur->next->val == val) {
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
} else {
cur = cur->next;
}
}
head = dummyHead->next;
delete dummyHead;
return head;
}
};
创建dummyNode作为虚拟头结点,不存储任何元素。
这样只需要cur即可,不用处理头结点。
注意:返回的是头节点,所以应该返回dummyNode->next!
三、过程中遇到的问题
Line 12: Char 14: runtime error: member access within null pointer of type ‘struct ListNode’ [solution.c]
报错原因:试图访问空指针。
707.设计链表
一、做题感受&第一想法
使用虚拟头结点,同时头结点的val域可用于记录链表实际长度。
206.反转链表
一、做题感受&第一想法
①翻转链表:头插法建立一个新链表
struct ListNode* reverseList(struct ListNode* head) {
struct ListNode* p = NULL, *q = head, *t = NULL;
p->next = NULL; //p作为新链表的dummy head
while(q != NULL){
t = q;
q = q->next;
t->next = p->next;
p->next = t;
}
return p->next;
}
②一开始想到了头插法,但是犯了一个错误:让节点不断头插回“原链表”,导致循环无法结束,超出时间限制。
二、学习文章后收获
1.翻转链表方法一:双指针法
struct ListNode* reverseList(struct ListNode* head) {
struct ListNode *pre = NULL, *q = head, *t = NULL;
while(q != NULL){
t = q->next;
q->next = pre;
pre = q;
q = t;
}
return pre;
}
2.翻转链表方法二:递归法(略难理解,暂时还没有完全理解吸收。)
class Solution {
public:
ListNode* reverse(ListNode* pre,ListNode* cur){
if(cur == NULL) return pre;
ListNode* temp = cur->next;
cur->next = pre;
// 可以和双指针法的代码进行对比,如下递归的写法,其实就是做了这两步
// pre = cur;
// cur = temp;
return reverse(cur,temp);
}
ListNode* reverseList(ListNode* head) {
// 和双指针法初始化是一样的逻辑
// ListNode* cur = head;
// ListNode* pre = NULL;
return reverse(NULL, head);
}
};