理论基础:
链表定义:是一种通过指针串联在一起的线性结构,每一节点由两部分组成:数据域和指针域(存放指向下一节点的指针),最后一个节点的指针域指向null,链表的入口节点称为头节点:head
链表的类型:
单链表;双链表:每一个链表有两个指针域,一个指向上一节点,一个指向下一节点
循环链表:解决约瑟夫环的问题
链表的存储方式:散乱分布,分布机制取决于操作系统的内存管理
链表的定义:
struct ListNode{
int val;//节点上存储的元素
ListNode *next;//指向下一节点的指针
ListNode(int x):val(x), next(NULL){}//节点的构造函数
};
//初始化节点
ListNode* head = new ListNode(5);
链表的操作
删除节点,删除D节点,需要将C节点的next指针 指向E节点,并手动释放D节点内存
添加节点
性能分析
数组 数据量固定,频繁查询,较少增删
链表 数据量不固定,频繁增删,较少查询
给你一个链表的头节点和一个整数al,请你删除链表中所有满足Node.val==val的节点
struct ListNode{
int val;
ListNode *next;
ListNode(int x) : val(x),next(nullptr){}
};
class Solution{
public:
ListNode* removeElements(ListNode* head, int val){
//设置虚拟头节点
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;//将虚拟头节点指向head
ListNode* cur = dummyHead;
//遍历
while (cur->next != nullptr) {
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;
}
};
你可以选择使用单链表或者双链表,设计并实现自己的链表。
单链表中的节点应该具备两个属性: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
的节点。
主要意思:
- 获取链表第index个节点的数值
- 在链表的最前面插入一个节点
- 在链表的最后面插入一个节点
- 在链表第index个节点前面插入一个节点
要找到index的前一个节点,cur->next指向index
定义的虚拟头结点就能满足
- 删除链表的第索引个节点
class MyLinkedList {
public:
struct LinkedNode {
int val;
LinkedNode* next;
LinkedNode(int val):val(val), next(nullptr){}
};
MyLinkedList() {
dummyHead = new LinkedNode(0);
size = 0;
}
int get(int index) {
if (index < 0 || index > (size - 1)) return -1;
//注意,要求的是cur的val,cur就不包括dummyHead了
LinkedNode* cur = dummyHead->next;
while (index--) {
cur = cur->next;
}
return cur->val;
}
void addAtHead(int val) {
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* cur = dummyHead;
newNode->next = cur->next;
cur->next = newNode;
size++;
}
void addAtTail(int val) {
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* cur = dummyHead;
while (cur->next != nullptr) {
cur = cur->next;
}
cur->next = newNode;
size++;
}
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++;
}
void deleteAtIndex(int index) {
if (index < 0 || index > (size - 1)) return;
LinkedNode* cur = dummyHead;
while (index--) {
cur = cur->next;
}
//暂存要删除的index
LinkedNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
//删除后要赋值,否则就会变成野指针val随机,对下一次调用造成麻烦
tmp = nullptr;
size--;
}
void printLinkedList() {
LinkedNode* cur = dummyHead;
while (cur->next != nullptr) {
cout << cur->next->val << "";
cur = cur->next;
}
cout << endl;
}
private:
int size;
LinkedNode* dummyHead;
};
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* cur = head;
ListNode* pre = nullptr;
while(cur) {
//保存cur的下一节点
ListNode* tmp = cur->next;
//开始翻转
cur->next = pre;
//更新
pre = cur;
cur = tmp;
}
return pre;
}
};
双指针,fast先走n+1步,这样能保证fast走到null后,slow走到倒数第n个节点的前一个
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
//采用快慢指针:fast先走n+1步,fast走到null后,slow走到倒数第n个前一位。
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* fast = dummyHead;
ListNode* slow = dummyHead;
while (n-- && fast != nullptr) {
fast = fast ->next;
}
fast = fast->next;
while (fast != nullptr) {
fast = fast->next;
slow = slow->next;
}
//暂存
ListNode* temp = slow->next;
slow->next = slow->next->next;
delete temp;
return dummyHead->next;
}
};
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null
。
注意:求两个链表交点节点的指针。交点不是数值相等,而是指针相等。
1.curA指向链表A的头节点;carB指向链表B的头节点
2.求出两个链表的长度,并求出两个链表长度的差值,curA移动到和curB末尾对齐的位置
3.此时比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,找到交点
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* curA = headA;
ListNode* curB = headB;
//求链表长度
int lenA = 0, lenB = 0;
while (curA != NULL){
lenA++;
curA = curA->next;
}
while (curB != NULL) {
lenB++;
curB = curB->next;
}
curA = headA;
curB = headB;//恢复指针
//求差值,为了对齐末尾
if (lenB > lenA) {
swap(lenA, lenB);
swap(curA, curB);
}
int gap = lenA - lenB;
while (gap--) {
curA = curA->next;
}
//遇到curA和curB相同则返回
while(curA != NULL) {
if(curA == curB) {
return curA;
}
curA = curA->next;
curB = curB->next;
}
return curA;
}
};
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
思路:1.判断链表是否有环2.如果有环,如何找到这个环的入口
具体:两步走:1.fast两步,slow一步,在环内相遇点相遇,相遇后进行第二步,设置index1从此处出发,设置index2从head出发,在入口点相遇,就找到了入口点。
fast每次走两步,slow每次走1步,最后会在环内相遇,目的是求x
fast = x+y+n(y+z)
slow = x+y
fast = 2slow-->x+y +n(y+z) = 2(x+y)
n(y+z) = x+y
x = (n-1)y+nz= (n-1)(y+z)+z
当n=1,x=z说明:从头节点出发一个指针,从相遇节点也出发一个指针,这两个指针每次只走一个节点,那么当这两个指针相遇的时候就是环形入口的节点。
在相遇处,定义一个指针index1,在头节点定义index2
同时移动,相遇的地方就是入口节点
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
//两步走 fast走两步,slow走一步 在相遇点见面
//第二部:相遇点设置index1,开头设置index2 一起走 会在入口见面
ListNode* fast = head;
ListNode* slow = head;
while (fast != NULL && fast->next != NULL) {
fast = fast->next->next;
slow = slow->next;
//找到相遇点,开始进行第二步
if (fast == slow) {
ListNode* index1 = fast;
ListNode* index2 = head;
while (index1 != index2) {
index1 = index1->next;
index2 = index2->next;
}
return index1;
}
}
return NULL;
总结:
链表种类主要是:单链表,双链表,循环链表
链表存储方式:分散存储,通过指针连在一起
链表的增删改查
数组和链表在不同场景下的性能分析