目录
一、链表的理论知识
链表节点的定义
struct ListNode{
ListNode*next;
int val;
ListNode():val(0),next(nullptr){}
ListNode(int v):val(v),next(nullptr){}
ListNode(int v,ListNode*ptr):val(v),next(ptr){}
}
每个链表节点结构都包含一个next指针,一个val节点值。并且含有构造函数,结构体中的构造函数,可以直接将结构体成员变量当作private变量进行直接赋值使用。
二、设计链表
1、要设计一个链表类首先应该考虑的就是设计一个链表节点,以及节点的构造函数。
2、一个链表类中为了方便对链表进行操作,应该定义一个链表头节点变量,方便后续对该连表类的其它成员方法函数进行操作时给定操作基础。
3、在链表类的所有方法函数中,实际操作都不能对定义的链表头节点进行直接操作。因为链表头节点作为private变量,被改动后会保持当前状态,当多个成员方法函数切换使用时,此时头节点变量会混乱。因此各方法函数在对头节点变量进行访问时,需要copy一个临时头节点再进行操作。
注意:在构建链表类时,题目中的 MyLinkedList linkedList = new MyLinkedList();操作是一个内存分配操作,此时并没有构建节点,所以头节点初始化ListNode*head=nullptr。
class MyLinkedList {
private:
struct listNode{
listNode*next;
int val;
listNode(int value,listNode*ptr)
{
next=ptr;
val=value;
}
};
listNode*head=nullptr;
public:
MyLinkedList() {
//不需要构建节点
//head=new listNode(0,nullptr);
}
int get(int index) {
listNode* temp=head;
for(int i=0;i<index;i++)
{
temp=temp->next;
}
return temp->val;
}
void addAtHead(int val) {
listNode*node=new listNode(val,nullptr);
node->next=head;
head=node;
}
void addAtTail(int val) {
listNode* temp=head;
while(temp->next!=nullptr)
{
temp=temp->next;
}
listNode*node=new listNode(val,nullptr);
temp->next=node;
}
void addAtIndex(int index, int val) {
listNode* temp=head;
for(int i=0;i<index-1;i++)
{
temp=temp->next;
}
listNode*node=new listNode(val,nullptr);
listNode* temp1=temp->next;
node->next=temp1;
temp->next=node;
}
void deleteAtIndex(int index) {
listNode* temp=head;
for(int i=0;i<index-1;i++)
{
temp=temp->next;
}
temp->next=temp->next->next;
}
};
三、交换链表节点
1、交换链表节点
class Solution {
public:
ListNode* swapPairs(ListNode* root) {
ListNode* cur=new ListNode(0,root);
ListNode* reuslt=cur;
while(root!=nullptr&&root->next!=nullptr)
{
ListNode*temp=root->next;//保存中间节点temp
root->next=temp->next;//步骤1
temp->next=root;//步骤2
cur->next=temp;//步骤3
//更新cur节点、root节点,temp节点在上面可以根据root节点获取
cur=cur->next->next;
root=cur->next;
}
return reuslt->next;
}
};
2、重排链表
将链表节点都存进数组里面,然后遍历数组进行链表重排。
class Solution {
public:
void reorderList(ListNode* head) {
//第一步:先将所有链表节点都存进数组里
vector<ListNode*> node;
while(head!=nullptr)
{
node.push_back(head);
head=head->next;
}
ListNode* cur=new ListNode(0);
ListNode* result=cur;
//第二步:遍历数组首尾,重排链表
int start=0;
int end=node.size()-1;
while(end>start)
{
cur->next=node[start];
start++;
cur=cur->next;
cur->next=node[end];
end--;
cur=cur->next;
}
//奇数个节点时会出现的情况
if(start==end)
{
cur->next=node[end];
cur=cur->next;
}
//给链表加上尾节点
cur->next=nullptr;
head=result->next;
}
};
3、反转链表
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* cur=new ListNode(0);
while(head!=nullptr)
{
ListNode*temp=head->next;//保存原链表头节点的next节点
//将原链表头节点从原链表中剥离出来,放在cur->....->cur->next之间
head->next=cur->next;
cur->next=head;
//更新原链表的新的头节点
head=temp;
}
return cur->next;
}
};
二、快慢指针
1、回文链表
方法一是使用数组将链表各节点的值保存到数组中,再对数组判断回文;
方法二是将链表的后半段反转作为新的链表,和链表的前半段挨个比较节点值;
本次主要介绍方法二,加深快慢指针的使用印象,分为如下几步:
- 用快慢指针,快指针有两步,慢指针走一步,快指针遇到终止位置时,慢指针就在链表中间位置
- 同时用pre记录慢指针指向节点的前一个节点,用来分割链表
- 将链表分为前后均等两部分,如果链表长度是奇数,那么后半部分多一个节点
- 将后半部分反转 ,得cur2,前半部分为cur1
- 按照cur1的长度,一次比较cur1和cur2的节点数值
class Solution {
public:
bool isPalindrome(ListNode* head) {
if (head == nullptr || head->next == nullptr) return true;
ListNode* slow = head; // 慢指针,找到链表中间分位置,作为分割
ListNode* fast = head;
ListNode* pre = head; // 记录慢指针的前一个节点,用来分割链表
while (fast && fast->next) {
pre = slow;
slow = slow->next;
fast = fast->next->next;
}
pre->next = nullptr; // 分割链表
ListNode* cur1 = head; // 前半部分
ListNode* cur2 = reverseList(slow); // 反转后半部分,总链表长度如果是奇数,cur2比cur1多一个节点
// 开始两个链表的比较
while (cur1) {
if (cur1->val != cur2->val) return false;
cur1 = cur1->next;
cur2 = cur2->next;
}
return true;
}
// 反转链表
ListNode* reverseList(ListNode* head) {
ListNode* temp; // 保存cur的下一个节点
ListNode* cur = head;
ListNode* pre = nullptr;
while(cur) {
temp = cur->next; // 保存一下 cur的下一个节点,因为接下来要改变cur->next
cur->next = pre; // 翻转操作
// 更新pre 和 cur指针
pre = cur;
cur = temp;
}
return pre;
}
};
2、删除链表的倒数第N个节点
思路一:可以先第一次遍历链表,将每个节点都保存在一个数组元素中,然后再进行删除操作
思路二:双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。
千万注意:使用C/C++编程语言,移除链表节点后,千万不要忘记删除这个移除的节点!!!
//如果是题目中出现,删除第index个节点,可以直接链表从头到尾遍历过去,计数达到后删除
//如果是题目中出现,删除倒数第index个节点:
//思路一:可以先第一次遍历链表,将每个节点都保存在一个数组元素中,然后再进行删除操作
//思路二:双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
vector<ListNode*> array(30);
int i=0;
ListNode*result=new ListNode(0,head);
while(head!=nullptr)
{
array[i++]=head;
head=head->next;
}
int num=i-n+1;
if(num==1)//情况一:删除的是第一个节点
result=result->next;
else
array[num-2]->next=array[num-1]->next;//情况二:删除的不是第一个节点
return result->next;
}
};
三、链表相交或成环
1、链表相交
//看到链表问题,并且涉及到倒数N节点的判断时,都可想到数组
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
vector<ListNode *>arrayA(30000);
vector<ListNode *>arrayB(30000);
int i=0;
int j=0;
while(headA!=nullptr)
{
arrayA[i++]=headA;
headA=headA->next;
}
while(headB!=nullptr)
{
arrayB[j++]=headB;
headB=headB->next;
}
int tempA=i-1;
int tempB=j-1;
while(tempA>=0&&tempB>=0&&arrayA[tempA]==arrayB[tempB])
{
tempA--;
tempB--;
}
if(tempA==i-1)
return NULL;
else
return arrayA[tempA+1];
}
};
2、环形链表
除哈希表算法,还有“龟兔赛跑算法”
class Solution {
public:
bool hasCycle(ListNode *head) {
unordered_set<ListNode*> mat;
while(head!=nullptr)
{
if(mat.find(head)!=mat.end())
return true;
else
{
mat.insert(head);
head=head->next;
}
}
return false;
}
};
3、环形链表||
方法一:哈希表
一个非常直观的思路是:我们遍历链表中的每个节点,并将它记录下来;一旦遇到了此前遍历过的节点,就可以判定链表中存在环。借助哈希表可以很方便地实现。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
unordered_set<ListNode *> visited;
while (head != nullptr) {
if (visited.count(head)) {
return head;
}
visited.insert(head);
head = head->next;
}
return nullptr;
}
};
方法二:快慢指针
我们使用两个指针,fast与 slow。它们起始都位于链表的头部。随后,slow 指针每次向后移动一个位置,而 fast指针向后移动两个位置。如果链表中存在环,则 fast指针最终将再次与 slow指针在环中相遇。
因此,当发现 slow与 fast相遇时,我们再额外使用一个指针 ptr。起始,它指向链表头部;随后,它和 slow每次向后移动一个位置。最终,它们会在入环点相遇。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode *slow = head, *fast = head;
while (fast != nullptr) {
slow = slow->next;
if (fast->next == nullptr) {
return nullptr;
}
fast = fast->next->next;
if (fast == slow) {
ListNode *ptr = head;
while (ptr != slow) {
ptr = ptr->next;
slow = slow->next;
}
return ptr;
}
}
return nullptr;
}
};
参考代码随想录