Last updated on October 15, 2024 pm
学不懂链表,写个博客浅浅记录一下链表的学习叭。
一、数组与链表的区别
面试中除了会问到栈和队列的区别,还可能会询问数组(顺序表)与链表的区别。我们也可以先从熟悉的数据结构:数组,通过分析它和链表的区别来体会链表的特征。
二、单链表的介绍
1、单链表的结构
(1)图示
(2)代码
1 2 3 4 5
| struct Listnode { int data; Listnode* next; };
|
2、头插
1 2 3 4 5 6
| void Listcreatf(Listnode** head,int x) { Listnode* newnode=new Listnode {x,NULL}; newnode->next=*head; *head=newnode; }
|
注意:函数的第一个参数类型应该为二级指针,因为涉及到改变/初始化头节点,这个操作需要为一级指针的值赋值。
3、单链表的创建(尾插)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void Listcreatb(Listnode** head,int x) { Listnode* newnode=new Listnode {x,NULL}; if(*head==NULL){ *head=newnode; } else{ Listnode* tail=*head; while(tail->next!=NULL) tail=tail->next; tail->next=newnode; } }
|
4、链表的释放
1 2 3 4 5 6 7 8 9 10
| void Listfree(Listnode** head) { Listnode* cur; while(*head){ cur=*head; *head=cur->next; delete(cur); cur=nullptr; } }
|
5、链表的打印
1 2 3 4 5 6 7 8
| void Listprint(Listnode* head) { listnode* cur=head; while(cur){ cout<<cur->data<<"->"; cur=cur->next; } }
|
6、删除链表中的节点
(1)删除头节点
1 2 3 4 5 6 7 8 9 10
| void Listdeletef(Listnode** head) { if(*head==NULL) return; else{ Listnode* newnode=(*head)->next; delete(*head); *head=newnode; } }
|
(2)删除尾节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| void Listdeleteb(Listnode** head) { if(*head==NULL) return; if((*head)->next==NULL){ delete(*head); *head=n; } else{ Listnode* cur=*head; Listnode* tmp=NULL; while(cur->next){ tmp=cur; cur=cur->next; } delete(cur); cur=NULL; tmp->next=NULL; } }
|
(3)删除等于给定值的所有节点
注意这个函数里会返回头节点因为函数参数只需要是一级指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| Listnode* Listdelete(Listdode* head,int x) { Listnode* tmp=NULL;Listnode* cur=head; while(cur){ if(cur->data==x){ if(cur==head){ head=cur->next; delete(cur); cur=head; } else{ tmp->next=cur->next; delete(cur); cur=tmp->next; } } else{ tmp=cur; cur=cur->next; } } return head; }
|
(4)删除指定位置的节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void Listdeletepos(Listnode** head,Listnode* pos) { if(*head==pos){ *head=pos->next; delete(pos); pos=nullptr; } else{ Listnode* cur=*head; while(cur->next!=pos){ cur=cur->next; } cur->next=pos->next; delete(pos); pos=nullptr; } }
|
7.链表元素的查找
1 2 3 4 5 6 7 8 9 10 11
| Listnode* Listfind(Listnode* head,int x) { Listnode* cur=head; while(cur){ if(cur->data==x) return cur; else cur=cur->next; } return NULL; }
|
8.在指定位置后插入节点
1 2 3 4 5 6
| void Listinsertb(Listnode* pos,int x) { Listnode* newnode=new Listnode {x,NULL}; newnode->next=pos->next; pos->next=newnode; }
|
三、单链表的应用&例题
1.链表反转
力扣题目连接:206. 反转链表 - 力扣(LeetCode)
方法一:迭代
1 2 3 4 5 6 7 8 9 10 11 12 13
| class Solution{ public: ListNode* reverseList(ListNode* head){ ListNode* prev=nullptr; ListNode* curr=head; while(curr){ ListNode* next=curr->next; curr->next=prev; pre=curr; curr=next; } } }
|
**->**图解分析:
方法二:递归
1 2 3 4 5 6 7 8 9 10 11
| class Solution{ public: ListNode* reverseList(ListNode* head){ if(!head||!head->next) return head; ListNode* newhead=reverseList(head->next); head->next->next=head; head->next=nullptr; return newhead; } }
|
2.找到链表的中间节点
力扣题目链接:876. 链表的中间结点 - 力扣(LeetCode)
题目描述:给你单链表的头结点 head
,请你找出并返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
方法一:遍历法
先遍历一遍找到一共有多少个节点,再遍历一遍找到中间节点。当链表长度较长的时候,这种方法较为浪费时间。下面的方法只需要遍历一次。
方法二:快慢指针法:
定义两个指针,慢指针依次只走一步,快指针一次走两步。当快指针走到终点时,慢指针就刚好走到了中间节点。
1 2 3 4 5 6 7 8 9
| ListNode* middleNode(ListNode* head) { ListNode* slow,ListNode* fast; slow=fast=head; while(fast&&fast->next){ slow=slow->next; fast=fast->next->next; } return slow; }
|
3.移除重复节点
力扣题目链接:面试题 02.01. 移除重复节点 - 力扣(LeetCode)
题目描述:编写代码,移除未排序链表中的重复节点。保留最开始出现的节点。
哈希表法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| ListNode* removeDuplicateNodes(ListNode* head) { ListNode* pre=NULL,*cur=head; unordered_set<int> list; while(cur){ if(list.find(cur>val)==list.end()){ list.insert(cur->val); pre=cur; } else pre->next=cur->next; cur=cur->next; } return head; }
|
4.找到链表的倒数第k个指针
力扣题目链接:
1 2 3 4 5 6 7 8 9 10 11
| int kthToLast(ListNode* head, int k) { ListNode* pre,*cur; pre=cur=head; while(k--) pre=pre->next; while(pre){ pre=pre->next; cur=cur->next; } return cur.val; }
|
5.判断链表是否为回文链表
力扣题目链接:
方法一:复制元素到数组中再进行判断
1 2 3 4 5 6 7 8 9 10 11 12
| bool isPalindrome(ListNode* head) { vector<int> list; while(head!=nullptr){ list.emplace_back(head->val); head=head->next; } for(int i=0,j=list.size()-1;i<j;i++,j--){ if(list[i]!=list[j]) return false; } return true; }
|
方法二:递归
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Solution { ListNode* frontPointer; public: bool recursivelyCheck(ListNode* currentNode) { if (currentNode != nullptr) { if (!recursivelyCheck(currentNode->next)) { return false; } if (currentNode->val != frontPointer->val) { return false; } frontPointer = frontPointer->next; } return true; }
bool isPalindrome(ListNode* head) { frontPointer = head; return recursivelyCheck(head); } };
|
6.相交链表
力扣题目链接:面试题 02.07. 链表相交 - 力扣(LeetCode)
题目描述:给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null
。题目数据 保证 整个链式结构中不存在环。注意,函数返回结果后,链表必须 保持其原始结构 。
双指针法:
1 2 3 4 5 6 7 8 9 10
| ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { if(headA==nullptr||headB==nullptr) return null; ListNode* n1=headA,*n2=headB; while(n1!=n2){ n1=n1==nullptr?headB:n1->next; n2=n2==nullptr?headA:n2->next; } return n1; }
|
7.环路检测
力扣题目链接:面试题 02.08. 环路检测 - 力扣(LeetCode)
题目描述:给定一个链表,如果它是有环链表,实现一个算法返回环路的开头节点。若环不存在,请返回 null。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
哈希表法:
1 2 3 4 5 6 7 8 9 10
| ListNode *detectCycle(ListNode *head) { unordered_set<ListNode*> list; while(head!=nullptr){ if(list.count(head)) return head; list.insert(head); head=head->next; } return null; }
|