整理/图片来源——链表理论基础 代码随想录
链表可以分为单链表,双链表和循环链表。
单链表如图1.1所示
双链表既可以向前查询也可以向后查询,如图1.2所示
数组是在内存中是连续分布的,但是链表在内存中不是连续分布的。
链表是通过指针域的指针链接在内存中各个节点。
链表的定义:
struct ListNode {
int val; // 节点上存储的元素
ListNode *next; // 指向下一个节点的指针
ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数
};
构造函数初始化节点:
ListNode* head = new ListNode(5);
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
//ListNode* pre = head;不能放在头上不然[7,7,7,7] 7会报错
while(head != nullptr && head->val == val) {//不是if是while,如果出现开头连续都是目标字符
//所有使用head->val/next都要先判断head是否可能为空
ListNode* cur = head;
head = head->next;
delete cur;
}
ListNode* pre = head;
while(pre != nullptr && pre->next != nullptr) {
if(pre->next->val == val) {
ListNode* cur = pre->next;
pre->next = pre->next->next;//已经换到下一个节点了不需要pre = pre->next;
delete cur;//别忘了删多余节点
}
else {
pre = pre->next;
}
//pre = pre->next;
//报错:runtime error: member access within null pointer of type 'ListNode' (solution.cpp)
//如果这里走到最后一个元素了,pre就为空了,进入while会对空指针操作(pre->next),所以对pre是否为空要在while里面有个判断
}
return head;
}
};
删除链表节点有两种办法:
1、直接使用原来的链表来进行删除操作。(第一次的代码)
2、设置一个虚拟头结点在进行删除操作。(后面的代码)
对于1,移除头结点和移除其他节点的操作是不一样的,因为链表的其他节点都是通过前一个节点来移除当前节点,而头结点没有前一个节点。所以头结点移除要将头结点向后移动一位。在写代码的时候也会发现,需要单独写一段逻辑来处理移除头结点的情况
对于2,设置一个虚拟头结点,这样原链表的所有节点就都可以按照统一的方式进行移除了。return 头结点的时候,别忘了 return dummyNode->next;, 这才是新的头结点
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dummy = new ListNode(0);//新建的时候new ListNode返回的为指针
dummy->next = head;
ListNode* pre = dummy;
while(pre != nullptr && pre->next != nullptr) {
//其实这里由于虚拟头节点肯定不为空,pre != nullptr 可以删了
if(pre->next->val == val) {
ListNode* cur = pre->next;
pre->next = pre->next->next;
delete cur;
}
else {
pre = pre->next;
}
}
head = dummy->next;//虚拟头节点别忘了删
delete dummy;
return head;
}
};
第一遍代码:
class MyLinkedList {
public:
struct LinkedNode
{
int val;
LinkedNode* next;
LinkedNode(int val):val(val), next(nullptr){}//{}别忘了
};
MyLinkedList() {
//加入虚拟头节点更方便,统一操作
dummy = new LinkedNode(0);
size = 0;
}
int get(int index) {
LinkedNode* cur = dummy;
if(index < 0 || index >= size) {
return -1;
}
for(int i = 0; i <= index; i++) {
cur = cur->next;
}
return cur->val;
}
void addAtHead(int val) {
LinkedNode* tmp = new LinkedNode(val);
tmp->next = dummy->next;
dummy->next = tmp;
size++;
}
void addAtTail(int val) {
LinkedNode* tmp = new LinkedNode(val);
LinkedNode* cur = dummy;
while(cur->next != nullptr) {
cur = cur->next;
}
cur->next = tmp;
size++;
}
void addAtIndex(int index, int val) {
LinkedNode* tmp = new LinkedNode(val);
LinkedNode* cur = dummy;
if(index < 0 || index > size) return;
while(index--) {
cur = cur->next;
}
tmp->next = cur->next;
cur->next = tmp;
size++;
}
void deleteAtIndex(int index) {
LinkedNode* tmp;
LinkedNode* cur = dummy;
if(index < 0 || index >= size) return;
while(index--) {
cur = cur->next;
}
tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
size--;
}
private:
LinkedNode* dummy;//**定义全局变量
int size;//要加一个大小不然不方便
};
每次某个节点要->next的时候想一想该节点可不可能为空
第一遍代码:
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(head == nullptr) return head;//每次要->next的时候想一想可不可能为空
ListNode* pre = head;
ListNode* cur = head->next;//当时需要记录几个节点,保证完成任务的同时可以走到下一个
head->next = nullptr;
while(cur != nullptr) {
ListNode* nextnode = cur->next;
cur->next = pre;
pre = cur;
cur = nextnode;
}
return pre;
}
};
pre可以直接搞成空的,这样可以少判断一个是否为空节点
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* temp; // 保存cur的下一个节点
ListNode* cur = head;
ListNode* pre = NULL;
while(cur) {
temp = cur->next; // 保存一下 cur的下一个节点,因为接下来要改变cur->next
cur->next = pre; // 翻转操作
// 更新pre 和 cur指针
pre = cur;
cur = temp;
}
return pre;
}
};
改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第一个节点了。首先要把 cur->next 节点用tmp指针保存一下,也就是保存下一个要操作的节点