链表基础
分类:
单链表、双链表、循环链表
类型
单链表#
链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
链表的入口节点称为链表的头结点也就是head。

双链表#
每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。
双链表 既可以向前查询也可以向后查询。

循环链表
循环链表首尾相连。
循环链表可以用来解决约瑟夫环问题。

存储方式
数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。
链表是通过指针域的指针链接在内存中各个节点。
所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。
如图所示:

这个链表起始节点为2, 终止节点为7, 各个节点分布在内存的不同地址空间上,通过指针串联在一起。
定义代码
//单链表
struct ListNode{
int val; // 节点存储的整数值
ListNode* next; // 指向下一个节点的指针
ListNode(int x): val(x),next(NULL) { } // 构造函数,初始化节点值为x,next指针置为NULL
};
此处为语雀内容卡片,点击链接查看:https://www.yuque.com/qingshanguren-vlalp/spemg2/ioad3wmr9d0vkacw
203.移除指定链表元素
只要将C节点的next指针 指向E节点就可以了。
D节点不是依然存留在内存里么?只不过是没有在这个链表里而已。
是这样的,所以在C++里最好是再手动释放这个D节点,释放这块内存。
其他语言例如Java、Python,就有自己的内存回收机制,就不用自己手动释放了。

ListNode* removeElements(ListNode* head, int val) {
// 设置一个虚拟头结点再进行移除节点操作
ListNode* dummyhead = new ListNode(0);
dummyhead->next = head;
ListNode* cur = dummyhead;
while (cur->next!= NULL) {
if(val == cur->next->val){
ListNode * tmp = cur->next;
cur->next = cur->next->next; //将指向要删除元素的指针指向下一个元素
delete tmp; //删除旧的节点
}
else{
cur = cur->next ;
}
}
head = dummyhead->next;
delete dummyhead; //删除旧的节点
return head;
}
707.设计链表
获取链表中下标为 index 的节点的值 : current指针从 头结点开始 移动index 次

int get(int index) {
if (index > (_size - 1) || index < 0) {
return -1;
}
LinkedNode* cur = _dummyHead->next;
while(index--){ // 如果--index 就会陷入死循环
cur = cur->next;
}
return cur->val;
}
易错点:
- 注意index是从0开始的,第0个节点就是头结点
- 所以判断条件是 index > (_size - 1) ,而不是 index > _size

// 在链表最前面插入一个节点,插入完成后,新插入的节点为链表的新的头结点
void addAtHead(int val) {
LinkedNode* newNode = new LinkedNode(val);
newNode->next = _dummyHead->next;
_dummyHead->next = newNode;
_size++;
}

// 在第index个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
// 如果index 等于链表的长度,则说明是新插入的节点为链表的尾结点
// 如果index大于链表的长度,则返回空
// 如果index小于0,则在头部插入节点
void addAtIndex(int index, int val) {
if(index > _size) return;
if(index < 0) index = 0;
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* cur = _dummyHead;
while(index--) {
cur = cur->next;
}
newNode->next = cur->next;
cur->next = newNode;
_size++;
}
易错点:添加新节点一定要按顺序,先把原来的cur->next 指针给 newNode->next
206.反转链表
只需要改变链表的next指针的指向,将链表反转
首先定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null。
然后就要开始反转了,首先要把 cur->next 节点用tmp指针保存一下,也就是保存一下这个节点。
为什么要保存一下这个节点呢,因为接下来要改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第一个节点了。
接下来,就是循环走如下代码逻辑了,继续移动pre和cur指针。

最后,cur 指针已经指向了null,循环结束,链表也反转完毕了。 此时我们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;
}
};
- 时间复杂度: O(n)
- 空间复杂度: O(1)
24. 两两交换链表中的节点

ListNode* swapPairs(ListNode* head) {
ListNode* dummyHead = new ListNode(0); // 设置一个虚拟头结点
dummyHead->next = head; // 将虚拟头结点指向head,这样方便后面做删除操作
ListNode* cur = dummyHead;
while(cur->next!=nullptr && cur->next->next!=nullptr){
ListNode* temp = cur->next; //存储1的地址
ListNode* temp1 = cur->next->next->next; //存储3的地址
cur->next = cur->next->next; //连接2节点
cur->next->next = temp; //2 连接 1 节点
cur->next->next->next = temp1; //1 连接 3 节点
cur = cur->next->next; // cur移动两位,准备下一轮交换
}
ListNode* result = dummyHead->next; // 最终结果的头节点(虚拟头的下一个)
delete dummyHead; // 释放虚拟头节点,避免内存泄漏
return result;
}
- 时间复杂度:O(n)
- 空间复杂度:O(1)
19.删除链表的倒数第N个节点
fast_ptr 用来跑到最后的,slow_ptr找到 倒数第n个节点的前面一个节点
slow_ptr 和 fast_ptr相差 n+1 步

ListNode* removeNthFromEnd(ListNode* head, int n) {
// 虚拟头节点:避免删除头节点时的边界问题
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* slow = dummyHead; // 慢指针(最终指向删除节点的前一个)
ListNode* fast = dummyHead; // 快指针(先跑n+1步)
// 快指针先跑n步
while(n-- && fast != nullptr) { // 推荐用nullptr替代NULL
fast = fast->next;
}
// 快指针再跑1步:让慢指针最终停在删除节点的上一个
fast = fast->next;
// 快慢指针一起跑,直到快指针到末尾
while (fast != nullptr) {
fast = fast->next;
slow = slow->next;
}
// 删除目标节点(注意释放内存,避免内存泄漏)
ListNode *tmp = slow->next; // 暂存要删除的节点
slow->next = tmp->next; // 跳过要删除的节点
delete tmp; // 释放内存
return dummyHead->next; // 返回真实头节点
}
面试题 02.07. 链表相交
同:160.链表相交

思路: 双指针法, 链表相交则最后几个节点肯定是一样的,双指针会指在同一个节点,所以要先尾端对齐
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *curA = headA;
ListNode *curB = headB;
int len_A=0, len_B =0;
while(curA != nullptr){ //计算链表A的长度
len_A++;
curA = curA->next;
}
while(curB != nullptr){ //计算链表B的长度
len_B++;
curB = curB->next;
}
curA = headA; //让两个指针重新指向头结点
curB = headB;
if(len_B > len_A){ //让 headA 指向 长链表
swap(curA,curB);
swap(len_A,len_B);
}
int gap = len_A - len_B;
while(gap--){
curA = curA->next;
}
while(curA!= nullptr){
if(curA == curB){
return curA;
}
curA = curA->next;
curB = curB->next;
}
return nullptr;
}
};
375

被折叠的 条评论
为什么被折叠?



