链表基础知识
- 链表中的每个节点至少包含两个部分: 数据域和指针域
- 链表中的每个节点, 通过指针域的值, 形成一个线性结构
- 查找节点 O(n), 插入节点O(1), 删除节点O(1)
- 不适合快速的定位数据, 适合动态的插入和删除数据的应用场景
实现方式
实现方式1
struct Node {
Node(int data) : data(data), next(NULL){}
int data;
Node *next;
}
int main(){
Node *head = Null;
head = new Node(1);
head->next = new Node(2)
head->next-> = new Node(3)
head->next->next->next = new Node(4)
//遍历
Node *p = head
while(p!=NULL){
print("%d->",p=>data) // 1->2->3->4->
p=p->next
}
printf("\n")
return 0;
}
实现方式2
int data[10]
int next[10]
void add(int ind, int p, int val){
next[p] = next[ind];
next[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);
int p = head;
while(p != 0){
printf("%d=>", data[p]); //0->1->2->3->100->
p = next[p];
}
printf("\n");
return 0 ;
}
链表的典型应用场景
场景一: 操作系统内的动态内存分配
场景二:LRU缓存淘汰算法
假设1GB缓存仅能存储4个数据, 那么取数据的时候, 先在这4个数据中查找, 如果查找到了, 那么即 缓存命中, 如果查不到, 就把数据5添加到链表的尾部, 从头部删除数据1, 保持仍是4个数据。
经典面试题 - 链表的访问
141.环形链表
判断链表中是否有环
思路1: 哈希表
- 依次遍历整个链表, 并创建一个哈希表来存储遍历过的节点
- 在存入哈希表之前, 先判断哈希表中是否存在该节点。如果不存在, 则存入哈希表。如果已经存在,说明遍历到了重复的点, 则存在环形链表
- 如果遇到next节点为null的节点, 说明没有环
思路2: 快慢指针
- 定义两个指针(一个慢指针, 一个快指针)
- 一开始, 慢指针指向head节点, 快指针指向head.next节点
- 快指针每次向前移动两步, 慢指针每次向前移动一步, 进行遍历整个链表
- 当快指针的next节点为null,或者快指针本身节点为null时, 说明该链表没有环, 遍历结束
- 如果链表有环, 那么快慢指针一定会相遇, 指向同一个节点, 当指向同一节点时, 遍历结束
class Solustion {
public:
bool hasCircle(listNode *head){
while(head == nullptr) return false;
//p 慢指针 q快指针
ListNode *p = head, *q = head->next;
while( p != q && q && q->next){
p = p->next;
q = q->next->next;
}
return q && q->next;
}
};
142 环形链表2
在一个指针在相遇点, 一个指针回到起点, 同时走, 相遇的位置既是环形的初始点
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if(head == nullptr) return nullptr;
ListNode *p = head, *q = head->next;
while( p != q && q && q->next){
p = p->next;
q = q->next->next;
}
if(q == nullptr || q->next == nullptr) return nullptr;
p = q = head;
do{
p = p->next;
q = q->next->next;
} while(p != q);
p = head;
while(p != q){
p = p->next;
q = q->next;
}
return q;
}
};
202 快乐数
一个数经过若干次变换规则(等于各位平方和), 若能变换到1, 即为1
题目可以转化为, 判断一个链表是否有环。
如果遍历某个节点为1, 说明没环, 就是快乐数。
如果遍历到重复的节点值, 说明有环, 就不是快乐数。
class Solution {
public:
int getNext(int x){
int z = 0;
while(x) {
z += ( x % 10 ) * (x % 10);
x /= 10;
}
return z;
}
bool isHappy(int n) {
int p = n, q = n;
do {
p = getNext(p);
q = getNext(getNext(q));
} while (p != q && q != 1);
return q == 1;
}
};
经典面试题 - 链表的反转
206 反转链表
基于迭代:
- 定义指针pre, pre指向空
- 定义cur,cur指向我们的头节点
- 定义指针next,next指向cur所指向节点的下一个节点
- 将cur指向pre, 然后移动指针pre到指针cur所在的位置, 移动cur到next所在的位置, 将next指针指西乡节点的下一个节点
- 不断重复, 当cur指针指向null的时候就完成了整个链表的反转
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(head == nullptr) return head;
ListNode *pre = nullptr, *cur = head, *p = head->next;
while(cur){
cur->next = pre;
pre = cur;
(cur = p) && (p = p->next);
}
return pre;
}
};
基于递归:
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;
}
};
92 反转链表2
将m到n区间的链表进行反转
技巧: 虚拟头节点
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 m, int n) {
ListNode ret(0, head),*p = &ret;
int cnt = n - m + 1;
while(--m) p = p->next;
p->next = reverseN(p->next,cnt);
return ret.next;
}
};
25 K个一组反转链表
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 *reverseN(ListNode *head, int n){
ListNode *p = head;
int cnt = n;
while(--n && p) p = p->next;
if(p == nullptr) return head;
return __reverseN(head, cnt);
}
ListNode* reverseKGroup(ListNode* head, int k) {
ListNode ret(0, head), *p = &ret, *q = p->next;
while((p->next = reverseN(q, k)) != q){
p = q;
q = p->next;
}
return ret.next;
}
};
61 旋转链表
class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
if(head == nullptr) return nullptr;
int n = 1;
ListNode *p = head;
while(p->next) p = p->next, n += 1;
p->next = head;
k %= n;
k = n-k;
while(k--) p = p->next;
head = p->next;
p->next = nullptr;
return head;
}
};
经典面试题 - 链表节点的删除
19 删除链表的倒数第N个结点
q指针先走n步, 然后q指针和p指针一起走, 知道q指向最后一个null节点, p就走到了倒数第N个结点的前一个结点
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode ret(0, head),*p = &ret, *q = head;
while(n--) q = q->next;
while(q) p = p->next, q = q->next;
p->next = p->next->next;
return ret.next;
}
};
83 删除排序列表的重复节点
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if(head == nullptr) return nullptr;
ListNode *p = head;
while(p->next){
if(p->val == p->next->val){
p->next = p->next->next;
}else{
p = p->next;
}
}
return head;
}
};
82 删除排序链表中的重复元素2
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
ListNode ret(0, head), *p = &ret, *q;
while(p->next){
if(p->next->next && p->next->val == p->next->next->val){
q = p ->next->next;
while(q && q->val == p->next->val) q = q->next;
p->next = q;
}else{
p = p->next;
}
}
return ret.next;
}
};
技巧: 什么时候需要使用虚拟头节点?
虚拟头节点用在链表的首地址有可能发生改变的情况