链表及其经典问题笔记
链表的基础补充知识
链表的定义与特性
1、链表中的每个结点至少包含两个部分:数据域与指针域;
2、链表中的每个结点,通过指针域存储的下一个结点地址,形成线性结构;
3、查找结点时间复杂度为O(n), 插入结点O(1),删除结点O(1);
4、不适合快速的定位数据,适合动态的插入和删除数据的应用场景;
几种经典的链表实现方法
//传统实现法
#include<iostream>
using namespace std;
struct Node {
Node(int val) : data(val), next(nullptr) {};
int data;
Node *next;
};
int main() {
Node *p = NULL;
p = new Node(2);
p->next = new Node(3);
p->next->next = new Node(4);
p->next->next->next = new Node(5);
while (p != NULL) {
cout << p->data << "->";
p = p->next;
}
cout << "NULL" << endl;
return 0;
}
//数组实现法
/*************************************************************************
> File Name: Linklist_2.cpp
> Author:
> Mail:
> Created Time: Wed 28 Feb 2024 08:33:21 PM CST
************************************************************************/
#include<iostream>
using namespace std;
int data[10] = {0};
int nxet[10] = {0};
void add(int ind, int p, int val) {
nxet[p] = nxet[ind];
nxet[ind] = p;
data[p] = val;
return;
}
int main() {
int head = 3;
data[3] = 0;
add(3, 5, 1);
add(5, 2, 2);
add(2, 7, 3);
add(7, 9, 100);
add(5, 6, 123);
int p = head;
while (p != 0) {
cout << data[p] << "->";
p = nxet[p];
}
cout << "NULL" << endl;
return 0;
}
链表的典型应用场景
场景一:操作系统内的动态内存分配
场景二:LRU缓存淘汰算法
经典面试题
链表的访问
leetcode141.环形链表
问题:判断链表是否为环装链表
思路1:【哈希表】
我们只需要使用哈希表存储已经访问过的结点,当存在冲突的时候就说明有环;
思路2:【双指针(快慢指针)】
快指针和慢指针同时出发,每次快指针走两步,慢指针走一步,如果有环就会相遇;
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
if (head == nullptr) return false;
ListNode *p = head, *q = head->next;
while (p != q && q && q->next) {
q = q->next->next;
p = p->next;
}
return q && q->next;
}
};
LeetCode142. 环形链表Ⅱ
问题:找到环状链表的起点位置
思路:【快慢指针】
找到快慢指针相遇的位置,将其中一个指针指向链表头结点,两个指针同时向前走相同步数,两指针再次相遇位置就是环装链表的起始位置。
/**
* 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) {
if (head == nullptr || head->next == nullptr) return nullptr;
ListNode *fast = head, *slow = head;
while (fast && fast->next) {
fast = fast->next->next;
slow = slow->next;
if (slow == fast) break;
}
if (slow != fast) return nullptr;
fast = head;
while (slow != fast) {
fast = fast->next;
slow = slow->next;
}
return fast;
}
};
LeetCode-202.快乐数
问题:判断一个数是不是快乐数
思路:【快慢指针】
利用快慢指针的技巧,思路上符合单链表的唯一指向性,因此是可以使用快慢指针判断链表是否存在环的方法;
class Solution {
public:
int getNext(int num) {
int ret = 0;
while (num) {
int x = num % 10;
ret += x * x;
num /= 10;
}
return ret;
}
bool isHappy(int n) {
int fast = n, slow = n;
do {
slow = getNext(slow);
fast = getNext(getNext(fast));
} while (fast != slow && fast != 1);
return fast == 1;
}
};
链表的反转
LeetCode206.反转链表
问题:反转链表
思路1:【三指针】
pre指向前面已经反转链表的部分,cur指向当前未反转的结点,next指向后面部分的链表首结点;
思路2:【递归】
利用递归函数的回溯过程,实现链表的反转,记录当前结点的下一个结点地址为tail(翻转后为尾巴),并将当前结点head的后面部分链表进行翻转并记录新的链表首结点位置为p,使得tail->next能够直接连上当前head,返回结点p为翻转链表的首结点;
//① 三指针
/**
* 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* reverseList(ListNode* head) {
ListNode *newHead = nullptr, *cur;
while (head) {
cur = head;
head = head->next;
cur->next = newHead;
newHead = cur;
}
return newHead;
}
};
//② 三指针
/**
* 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* reverseList(ListNode* head) {
if (head == nullptr) return nullptr;
ListNode *pre = nullptr, *cur = head, *p = head->next;
while (cur) {
cur->next = pre;
pre = cur;
(cur = p) && (p = p->next);
}
return pre;
}
};
//③ 递归
/**
* 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* reverseList(ListNode* head) {
if (head == nullptr || head->next == nullptr) return head;
ListNode *tail = head->next, *p = reverseList(head->next);
head->next = tail->next;
tail->next = head;
return p;
}
};
LeetCode92.反转链表Ⅱ
问题:将链表的第m个结点到第n个结点的链表进行反转
思路1:【递归 + 虚拟头结点】
使用虚拟头结点Dhead,将头结点head连接起来,指针p先走m步,调用指针翻转函数翻转第m和第n结点之间的m - n + 1个结点;
思路2:【三指针 + 虚拟头结点】
使用虚拟头结点Dhead, 将头结点head连接起来,指针q先走m步,然后q再走n步,调用反转函数翻转m和n之间的结点;
// ① 递归 + 虚拟头结点
/**
* 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 *reverseN(ListNode *head, int n) {
if (n == 1) return head;
ListNode *tail = head->next, *p = reverseN(head->next, n - 1);
head->next = tail->next;
tail->next = head;
return p;
}
ListNode* reverseBetween(ListNode* head, int left, int right) {
ListNode Dhead(0, head);
ListNode *p = &Dhead;
int cnt = right - left + 1;
while (--left) p = p->next;
p->next = reverseN(p->next, cnt);
return Dhead.next;
}
};
// ② 三指针 + 虚拟头结点
/**
* 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 *reverseNode(ListNode *head) {
ListNode *newHead = nullptr, *cur;
while (head) {
cur = head;
head = head->next;
cur->next = newHead;
newHead = cur;
}
return newHead;
}
ListNode* reverseBetween(ListNode* head, int left, int right) {
ListNode *DH = new ListNode(0);
DH->next = head;
ListNode *h1 = DH, *h2 = DH, *h3 = DH;
ListNode *pre = h2;
while (left--) {
if (left == 0) pre = h2;
h2 = h2->next;
}
while (right--) {
h3 = h3->next;
}
ListNode *tmp = h3->next;
h3->next = nullptr;
h2 = reverseNode(h2);
pre->next = h2;
while (h2->next) h2 = h2->next;
h2->next = tmp;
return DH->next;
}
};
LeetCode25.K个一组翻转链表
问题:K个一组翻转链表
思路1:【三指针 + 虚拟头结点】
正常使用三指针进行翻转,设置虚拟头结点,使用指针p和q分别指向待翻转长度为k的链表的头结点和尾结点,每次使用cur和记录剩余待翻转的链表的首节结点,并迭代进行翻转;
思路2:【递归 + 虚拟头结点】
设置一个虚拟头结点,连接链表头head, 使用指针p和q分别指向待翻转链表头结点的前一个结点,和待反转链表的当前结点,使用while循环迭代保证p和q指针指向的一直是待翻转链表的头结点的前一个结点和待翻转链表的当前结点;
// ① 迭代
/**
* 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 *reverseNode(ListNode *head) {
ListNode *newHead = nullptr, *cur;
while (head) {
cur = head;
head = head->next;
cur->next = newHead;
newHead = cur;
}
return newHead;
}
ListNode* reverseKGroup(ListNode* head, int k) {
if (head == nullptr || head->next == nullptr) return head;
ListNode *dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode *cur = dummyHead, *start = head, *end = dummyHead;
while (end && end->next) {
for (int i = 0; i < k && end != nullptr; i++) {
end = end->next;
}
if (end == nullptr) break;
start = cur->next;
ListNode *next = end->next;
end->next = nullptr;
cur->next = reverseNode(start);
start->next = next;
cur = start;
end = cur;
}
return dummyHead->next;
}
};
// ② 递归
/**
* 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 *__reverseN(ListNode *head, int k) {
if (k == 1) return head;
ListNode *tail = head->next, *p = __reverseN(head->next, k - 1);
head->next = tail->next;
tail->next = head;
return p;
}
ListNode *reverseN(ListNode *head, int k) {
int cnt = k;
ListNode *p = head;
while (--cnt && p) p = p->next;
if (p == nullptr) return head;
return __reverseN(head, k);
}
ListNode* reverseKGroup(ListNode* head, int k) {
ListNode DH(0, head), *p = &DH, *q = p->next;
while ((p->next = reverseN(q, k)) != q) {
p = q;
q = p->next;
}
return DH.next;
}
};
LeetCode61.旋转链表
问题:旋转链表
思路1:【成环 + 找到最后一位结点】
先遍历一遍链表的长度,然后指针p记录链表的尾结点, 将链表的头尾结点相连(p->next = head),移动p指针K步,在进行断链操作,获取新的头结点为p->next;
// ①
/**
* 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* rotateRight(ListNode* head, int k) {
if (head == nullptr || head->next == nullptr) return head;
ListNode *p = head;
int cnt = 1;
while (p->next) p = p->next, cnt++;
p->next = head;
cnt -= k % cnt;
while (cnt--) p = p->next;
head = p->next;
p->next = nullptr;
return head;
}
};
LeetCode24.两两交换链表结点
问题:两两交换链表结点
思路1:【递归】or 【迭代】
K个一组链表翻转的特例
//① 迭代
/**
* 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* swapPairs(ListNode* head) {
ListNode *dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode *cur = dummyHead;
while (cur && cur->next && cur->next->next) {
ListNode *tmp1 = cur->next;
ListNode *tmp2 = cur->next->next;
tmp1->next = tmp2->next;
tmp2->next = tmp1;
cur->next = tmp2;
cur = tmp1;
}
return dummyHead->next;
}
};
链表结点的删除
LeetCode19.删除链表的倒数第N个结点
问题:删除链表的倒数第N个结点
思路:【遍历】
设置虚拟头结点,找到待删除结点cur前一个结点pre,pre->next = cur->next;
/**
* 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* removeNthFromEnd(ListNode* head, int n) {
ListNode *DH = new ListNode(0);
DH->next = head;
ListNode *pre = head;
while (n--) {
pre = pre->next;
}
ListNode *cur = DH;
while (pre) {
cur = cur->next;
pre = pre->next;
}
ListNode *tmp = cur->next;
cur->next = tmp->next;
return DH->next;
}
};
LeetCode83.删除链表中的重复结点
问题:删除链表中的重复结点
思路:【指针迭代】
使用指针p指向头结点,循环迭代判断p与p->next 的值是否相等,如果相等,那么就p->next = p->next->next, 否则p = p->next;
/**
* 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* deleteDuplicates(ListNode* head) {
if (head == nullptr || head->next == nullptr) return head;
ListNode *p = head;
while (p->next) {
if (p->val != p->next->val) p = p->next;
else p->next = p->next->next;
}
return head;
}
};
LeetCode82.删除有序链表的重复结点
问题:删除有序链表有重复的结点
思路:【遍历 + 虚拟头结点】
设置一个虚拟头结点DH,指针p指向DH,循环判断条件p->next不为空,然找到第一个不等于p->next->val的结点,并将p->next 指向该节点;如果没有找打重复结点,就将p = p->next;
/**
* 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* deleteDuplicates(ListNode* head) {
if (head == nullptr || head->next == nullptr) return head;
ListNode *dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode *cur = dummyHead;
while (cur && cur->next && cur->next->next) {
if (cur->next->val == cur->next->next->val) {
int x = cur->next->val;
while (cur->next && x == cur->next->val) {
ListNode *tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
}
} else {
cur = cur->next;
}
}
return dummyHead->next;
}
};