链表基础认知
链表是一种通过指针串联在一起的结构,相较于数组来说更适合增删不适合查询,增删只需要将前一个结点的指针指向新节点,新节点指向后一个节点即可。
链表分为:单链表,双链表(有两个指针域,一个指向前,一个指向后),循环链表
链表在内存中的分布不连续,散乱分布,取决于操作系统的内存管理。删除节点时候最好手动释放
单链表定义:
struct ListNode{
int val;
ListNode *next;
ListNode(int x): val(x),next(null){}
};
链表的构造函数最好自己定义,如果使用默认的构造函数,不能直接初始化给变量赋值,需要另起一行:head- >val =5(例如)
移除链表元素
删除链表中等于给定值 val 的所有节点。
比较好的方法是使用虚拟头节点,这道题的逻辑比较简单,但细节比较多,避免内存泄漏需要temp来存储被删除的变量然后再手动delete掉,创建虚拟头节点最后也要delete掉
时间复杂度O(n) 空间复杂度O(1)
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* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* cur = dummyHead;
while (cur->next != NULL) {
if (cur->next->val == val)
{
ListNode* temp = cur->next;
cur->next = cur->next->next;
delete temp;
}
else {
cur = cur->next;
}
}
head = dummyHead->next;
delete dummyHead;
return head;
}
};
设计链表
题目:实现如下功能
- get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
- addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
- addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
- addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
- deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
- 时间复杂度: 涉及
index
的相关操作为 O(index), 其余为 O(1) - 空间复杂度: O(n)
- 前摇
-
class MyLinkedList { public: struct LinkedNode { int val; LinkedNode* next; LinkedNode(int val):val(val), next(nullptr){} }; MyLinkedList() { _dummyHead = new LinkedNode(0); _size = 0; }
注意,链表无法通过size获取他的大小
找数值,链表根据节点找数字需要一个一个从头往后找,所以在确定查找到index范围之后,通过一个while循环确定index的前进格数,cur不断前进(后面赋值给前一个)最后找到目标值
int get(int index) {
if (index > (_size - 1) || index < 0) {
return -1;
}
LinkedNode* cur = _dummyHead->next;
while (index--) {
cur = cur->next;
}
return cur->val;
}
头插法,逻辑很简单,就是三步,创建新节点,新节点指向第一个节点,虚拟头节点的指针指向新节点
尾插法,唯一的难点就是要找到这个尾在哪,可以通过while一个个找到尾,毕竟链表只有指针的关系,没有整体意识
void addAtHead(int val) {
LinkedNode* newNode = new LinkedNode(val);
newNode->next = _dummyHead->next;
_dummyHead->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++;
}
删节点,也是先排除特殊情况,然后从头开始找目标删除的前一个数index,再把cur->next 置给tmp,链接下下个数值,记得释放内存时候一定要释放数值和指针两步!不然野指针会乱跑
void deleteAtIndex(int index) {
if (index >= _size || index < 0) {
return;
}
LinkedNode* cur = _dummyHead;
while(index--) {
cur = cur ->next;
}
LinkedNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
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;
};
总结: 相对简单的综合题,不费脑但费时,需要仔细仔细
反转链表
直接改变链表指针的指向,通过两个指针cur和pre , 将cur->next指向pre, 核心就四步:1.先给cur->next保存 2.pre赋值给cur->next,反转指向 3.将cur给pre 4.将temp给cur 实现继续向下走
- 时间复杂度O(n) 空间复杂度O(1)
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* temp;
ListNode* cur = head;
ListNode* pre = NULL;
while (cur) {
temp = cur->next;
cur->next = pre;
pre = cur;
cur = temp;
}
return pre;
}
};
两两交换链表中的节点
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
看似两两交换,其实涉及了三个元素的变换,在核心的指针反转之外,要记得要将第一个指针和连着的第三个指针给赋最开始的指针位置,所以要用临时tmp保存,同时因为指针的缘故,设置一个虚拟头节点比较好操作,主要就是,1 2 3变成了1 3 2 这里面的指针变换值得重视。
- 时间复杂度O(n) 空间复杂度O(1)
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* cur = dummyHead;
while( cur->next != nullptr &&cur->next->next != nullptr) {
ListNode* tmp = cur->next;
ListNode* tmp1 = cur->next->next->next;
cur->next = cur->next->next;
cur->next->next = tmp;
cur->next->next->next = tmp1;
cur = cur->next->next;
}
ListNode* result = dummyHead->next;
delete dummyHead;
return result;
}
};
删除链表的倒数第N个节点
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
这题有前面的基础也比较简单,核心:通过双指针,快的和慢的形成n+1个差
时间复杂度O(n) 空间复杂度O(1)
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next =head;
ListNode* slow = dummyHead;
ListNode* fast = dummyHead;
while(n--&&fast!=NULL){
fast = fast->next;
}
fast= fast->next;
while(fast!=NULL){
fast = fast->next;
slow = slow->next;
}
ListNode* tmp = slow->next;
slow->next = tmp->next;
delete tmp;
return dummyHead->next;
}
};
链表相交
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
图示两个链表在节点 c1 开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
链表相交本质上是求两个链表有没有相同的指针,再取出指针指向的值 ,相交有一个特点是从交点往后,后面的几个元素会相同,换句话说,假如有交点,从后往前一定是一段相同的链表,所以可以通过遍历,让A>B,然后从后往前对齐链表,再两个遍历一个个往前找的方式进行。
思路主要是先从头遍历得到两个链表长度,再用swap得到lenA>lenB,然后通过gap差,先让A自己走到一样的起始位置,再while if,找到交点。注意:判断有没有走到头要用curA/B!=null
时间复杂度:O(n + m) 空间复杂度:O(1)
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* curA = headA;
ListNode* curB = headB;
int lenA =0,lenB = 0;
while(curA!=0){
lenA++;
curA= curA->next;}
while(curB!=0){
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;}
while(curA != NULL){
if(curA ==curB){
return curA;}
curA = curA->next;
curB= curB->next;
}
return NULL;
};
环形链表Ⅱ
题意: 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
先是通过两个异步指针找到相交,然后再通过index两个指针同步找到环的入口,详细过程建议看原网站 代码随想录 (programmercarl.com)
- 时间复杂度: O(n),快慢指针相遇前,指针走的次数小于链表长度,快慢指针相遇后,两个index指针走的次数也小于链表长度,总体为走的次数小于 2n
- 空间复杂度: O(1)
class Solution {
public:
ListNode* deterCycle(ListNode* head) {
ListNode* fast = head;
ListNode* slow = head;
while (fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) {
ListNode* index1 = fast;
ListNode* index2 = head;
while (index1 != index2) {
index1 = index1->next;
index2 = index2->next;
}
return index2;
}
}
return NULL;
}