🔥博客主页: A_SHOWY
🎥系列专栏:力扣刷题总结录 数据结构 云计算
203.移除链表元素 | easy | 虚拟头结点简便 |
707.链表的各种操作(六个小题目) | mid | 基础操作,熟悉 |
206.翻转列表 | easy | 双指针和递归方法,核心都是双指针 |
24.两两交换链表中的节点 | mid | 交换的步骤不能颠倒,同时提前设置tmp |
19.删除链表中的倒数第n个数 | mid | 如何找到倒数第n个数,双指针:快指针先走n,然后一起走 |
面试题 链表相交 | easy | swap()函数 |
142. 环形链表Ⅱ‘ | mid | 双指针方法,快慢指针 |
一、链表基础理论
(1)什么是链表
1.链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成
2.数据域和指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针)
3.链表的入口结点为链表的头结点也就是head
(2)链表的类型
1.单链表:上图简单的链表
2.双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点
3.循环链表:就是链表首位连接
(3)链表的存储方式
同数组不同的是,链表的内存不是不是连续分布的,而是通过指针来链接内存的各个结点
//单链表
struct(ListNode)
{
int val;//结点上存储的元素
ListNode *next//指向下一个节点的指针
ListNode(int x) : val(x) , next(NULL){}//节点的构造函数
}
这个构造函数的作用是可以初始化节点
(1)如果通过自己构造函数初始化节点
ListNode* head = new ListNode(5);
(2) 如果使用默认构造函数的话,初始化时不能直接给变量赋值
ListNode* head = new ListNode();
head -> val = 5;
(4)链表的操作
1.删除节点时,只需要把指针指向下一个节点。再释放删除的内存空间。但是在删除头结点时,比较特殊需要head = head -> next
特殊: 删除第n个节点时,需要从头结点用next指针查询,时间复杂度为o(n)
2.增加节点时,申请一个空间,前一个指针指到这里,新空间的指针指向下一个节点
二、 移除链表元素
在前面提到,是否是头结点的删除元素方式不统一,为了统一,我们设置新的方法创建虚拟头结点
(1) 203. 移除链表元素
203. 移除链表元素https://leetcode.cn/problems/remove-linked-list-elements/1. 第一种方法是分开讨论
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
//第一种方法,是否删除的是头结点分开考虑
//删除头结点
while(head != NULL && head->val ==val)
{
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;
}
};
2.第二种方法是创建一个虚拟头结点一起讨论
/**
* 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* dummyhead = new ListNode(0);//创建新的虚拟头结点
dummyhead -> next = head;
ListNode* cur = dummyhead;
while( cur ->next != NULL)//这里需要注意不是if
{
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;
}
};
这里有两个个点需要注意:
第一点是创建的虚拟头结点需要delete
第二点是因为是一个不断观察是否等于val的过程,所以要用while而不是if
三、设计链表
(1)707. 链表的各种操作(基础)
707. 设计链表https://leetcode.cn/problems/design-linked-list/有几个需要注意的点:
1.首先是每逢遍历整个链表之前先设立一个指针cur用来专门遍历,不然返回头指针会受阻
2. 在删除下标为index的结点时候,delete只是释放了原本tmp 指针指向的部分的内存,delte后并不是指针tmp的值并不是NULL,会成为随机值,所以需要
tmp = nulltpr;
防止他成为乱指向的野指针。
3. 定义结构体的时候别忘了最后的括号
4. 初始化链表前声明私有成员变量
private:
int _size;
LinkedNode* _dummyhead;
class MyLinkedList {
public:
struct LinkedNode{
//定义链表结构体
int val;
LinkedNode* next;//定义了一个名为 next 的指针变量
LinkedNode(int val): val(val),next(nullptr){}
};
//初始化链表
MyLinkedList() {
_dummyhead = new LinkedNode(0);//定义虚拟的头结点
_size = 0;
}
//获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1
int get(int index) {
if(index < 0 || index >= _size)
{
return -1;
}
LinkedNode* cur = _dummyhead -> next;
while(index--)
{
cur = cur -> next;
}
return cur->val;
}
//将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点
void addAtHead(int val) {
LinkedNode* newcode = new LinkedNode(val);
newcode -> next = _dummyhead -> next;
_dummyhead -> next = newcode;
_size ++;
}
//将一个值为 val 的节点追加到链表中作为链表的最后一个元素。
void addAtTail(int val) {
LinkedNode* newcode = new LinkedNode(val);
LinkedNode* cur = _dummyhead;//不能用next
while(cur -> next != nullptr)
{
cur = cur -> next;
}
cur -> next = newcode;
_size ++;
}
// 将一个值为 val 的节点插入到链表中下标为 index 的节点之前。如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将不会插入到链表中
void addAtIndex(int index, int val) {
LinkedNode* newcode = new LinkedNode(val);
if(index > _size) return;
if(index < 0) index = 0;
LinkedNode* cur = _dummyhead;
while(index --)
{
cur = cur -> next;
}
newcode -> next = cur -> next;
cur -> next = newcode;
_size ++;
}
//如果下标有效,则删除链表中下标为 index 的节点。
void deleteAtIndex(int index) {
if(index < 0 || index >= _size) 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;
};
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList* obj = new MyLinkedList();
* int param_1 = obj->get(index);
* obj->addAtHead(val);
* obj->addAtTail(val);
* obj->addAtIndex(index,val);
* obj->deleteAtIndex(index);
*/
四、 翻转链表
(1) 206. 翻转链表
1. 双指针法
//方法1 双指针的方法
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* tmp;//很重要因为要翻转链表 cur指向pre时,无法联系后边的链表,所以提前存上
ListNode* cur = head;
ListNode* pre = NULL;
while(cur)
{
tmp = cur -> next;
cur -> next = pre;
pre = cur;
cur = tmp;//注意这两行不能换位置
}
return pre;
}
};
双指针方法有两个点需要注意:
1. ListNode* tmp;很重要因为要翻转链表 cur指向pre时,无法联系后边的链表,所以提前存上
2.注意两个指针同时向前移动时操作的顺序
2.递归方法
核心思想和双指针一样,只是代码更加简约
//递归方法,巧妙,核心还是双指针
ListNode* reverse(ListNode* pre,ListNode* cur)
{
if(cur == NULL) return pre;
ListNode* tmp = cur -> next;
cur -> next = pre;
return reverse(cur,tmp);
}
ListNode* reverseList(ListNode* head) {
return reverse(NULL,head);
}
};
想清楚上面那张图,递归就能很快对应写出来。
五、两两交换链表中的结点
(1)24.两两交换的链表中的节点
24. 两两交换链表中的节点https://leetcode.cn/problems/swap-nodes-in-pairs/
1. 本题目的交换过程一共有三个步骤,但是首先就是要防止指针变换导致的节点丢失,需要提前标注出临时指针tmp和tmp1,然后的操作就是dummyhead指向2,2指向1,1指向3.然后cur向后走两位
2.还有一个要注意的是,设置dummyhead的作用是头结点交换的时候后续操作保持一致
3.在循环的时候要考虑奇数个还是偶数个,所以在while循环的时候,要考虑两种情况
while(cur -> next != nullptr && cur -> next -> next != nullptr)至于为什么要用&& 表示为只有后一个节点和后后的节点都不为NULL时,才继续循环,如果用|| (或)会在奇数个节点时陷入死循环
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;//第二步
tmp -> next = tmp1;//第三步
cur = cur -> next -> next;
}
return dummyhead -> next;
}
};
六、删除链表中倒数第n个数(寻找倒数第n个数)
(1)19.删除链表中倒数第n个数
19. 删除链表的倒数第 N 个结点https://leetcode.cn/problems/remove-nth-node-from-end-of-list/
1.这道题的关键思路是如何找到倒数第n个数 思路:双指针快慢指针,快指针先走n,快慢指针一起走,直到快指针指到NULL。
2.但是要删除第n个数,需要操作第n-1个数,才能删除第n个
3.同样需要定义虚拟头结点
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyhead = new ListNode(0);
dummyhead -> next = head;
ListNode* slow = dummyhead;
ListNode* fast = dummyhead;
n++;
while(n--)
{
fast = fast -> next;
}
while(fast != NULL)
{
fast = fast -> next;
slow = slow -> next;
}
slow -> next = slow -> next -> next;
return dummyhead -> next;
}
};
七、链表相交
面试题 02.07. 链表相交https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/
(1)面试题 链表相交
1.找后半部分的相同的部分,并返回头指针,其主要思路是先求一下两个链表的长度,然后先找出长的那个指针,先让他走len1-len2,然后再每个节点逐个判断
2.因为输入的两个链表不知道哪个是长的,如何让cur1 指向那个长度比较长的链表是很关键的,所以用到swap函数,让cura为最长链表的头,同时len1表示长的那个
3.在确定两个链表长度以后,一定不要忘记初始化cura和curb。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* cura = headA;
ListNode* curb = headB;
int len1 = 0;
int len2 = 0;
//求第一个链表长度
while(cura != NULL)
{
len1 ++;
cura = cura -> next;
}
//求第二个链表长度
while(curb != NULL)
{
len2 ++;
curb = curb -> next;
}
//把cura为最长链表的头,len2为其长度
cura = headA;
curb = headB;
if(len2 > len1)
{
swap(len1 ,len2);
swap(cura , curb);
}
int length = len1 - len2;
while(length--)
{
cura = cura -> next;
}
while(cura != NULL)
{
if(cura == curb) {return cura;}
cura = cura -> next;
curb = curb -> next;
}
return NULL;
}
};
八、环形链表(判断是否有环)
(1)142. 环形链表Ⅱ
142. 环形链表 IIhttps://leetcode.cn/problems/linked-list-cycle-ii/1.首先第一个目标是判断是否有环,那么根据经验是快慢指针,那么为什么会使用快慢指针,因为首先快指针永远走的快,慢指针永远走的慢。当有环时,快指针通过环回到原来的点,和慢指针相遇才有可能有环。
2.第二个问题就是如何找到环的入口处,通过快指针走2,慢指针走1,设置参数可以得出来,设置两个指针 ,一个从相交点走,一个从head走,相遇的时候刚好是环的入口,如图。
3.第三个点是为什么设置slow 的时候不用设置成转了很多圈是因为,我们设置了fast是slow 的二倍,所以在slow进入后,取一个极限情况,fast刚过,那么依然能够追上在一圈之内。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* fast = head;
ListNode* slow = head;
while(fast != NULL && fast ->next != NULL)//这里fast每次走两个,所以不光要判断fast还要判断fast的下一位
{
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;
}
};