本篇博客来探讨数据结构当中的链表,并且来手撕一些经典算法OJ题,OJ题已插入超链接,点击可直接跳转~
一、链表的概念与结构
1.概念
链表是物理存储结构上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
2.链表的分类
(1)单向或者双向
(2)带头或者不带头
带头:指的是带有哨兵位head,head不存储具体的信息,只是作为一个站岗的位置,方便链表的操作
(3)循环或者非循环
二、链表经典算法OJ题
1.返回倒数第k个节点
(1)题目描述
实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。
(2)题目分析
核心思路:快慢指针
- 第一步:fast指针先走k步
- 第二步:fast和slow指针同时++,往后一步步走,直到fast=NULL
具体代码实现如下
int kthToLast(struct ListNode* head, int k)
{
struct ListNode*fast=head;
struct ListNode*slow=head;
k=k-1;
while(k--)
{
fast=fast->next;
}
while(fast->next!=NULL)
{
fast=fast->next;
slow=slow->next;
}
return slow->val;
}
2.链表的回文结构
(1)题目描述
对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。
对回文结构的理解:链表从中间砍成两半,如果对称即为回文
(2)题目分析
- 第一步:先找中间节点
思路:快慢指针——fast的速度为slow的二倍,则fast指针指向链表末尾的时候,slow指向链表中间节点,此处需要理清什么是中间节点?
红色箭头所指就是中间节点
具体代码实现如下
ListNode* findMid(ListNode*A)
{
ListNode*fast=A;
ListNode*slow=A;
while(fast&&fast->next)
{
fast=fast->next->next;
slow=slow->next;
}
return slow;
}
- 第二步:分成两半,将后半段逆置
本题算法可以关联题目“反转链表”
思路:创建三个指针
具体代码实现如下
ListNode*reverse(ListNode*mid)
{
ListNode*n1=NULL;
ListNode*n2=mid;
ListNode*n3=mid->next;
while(n2)
{
n2->next=n1;
n1=n2;
n2=n3;
if(n3)
{
n3=n3->next;
}
}
return n1;
}
- 第三步:将前半段的链表和后半段的链表进行比较,用两个指针进行遍历,直到其中一个指针为NULL,根据遍历情况确定返回true还是false
具体代码实现如下
bool chkPalindrome(ListNode* A)
{
ListNode*mid=findMid(A);
ListNode*remid=reverse(mid);
ListNode*p1=A;
ListNode*p2=remid;
while(p1&&p2)
{
if(p1->val!=p2->val)
{
return false;
}
p1=p1->next;
p2=p2->next;
}
return true;
}
完整代码呈现如下(C++兼容C,网页内虽然没提供C语言环境,但也可以直接用C语言写)
class PalindromeList
{
public:
ListNode* findMid(ListNode*A)
{
ListNode*fast=A;
ListNode*slow=A;
while(fast&&fast->next)
{
fast=fast->next->next;
slow=slow->next;
}
return slow;
}
ListNode*reverse(ListNode*mid)
{
ListNode*n1=NULL;
ListNode*n2=mid;
ListNode*n3=mid->next;
while(n2)
{
n2->next=n1;
n1=n2;
n2=n3;
if(n3)
{
n3=n3->next;
}
}
return n1;
}
bool chkPalindrome(ListNode* A)
{
ListNode*mid=findMid(A);
ListNode*remid=reverse(mid);
ListNode*p1=A;
ListNode*p2=remid;
while(p1&&p2)
{
if(p1->val!=p2->val)
{
return false;
}
p1=p1->next;
p2=p2->next;
}
return true;
}
};
3.相交链表
(1)题目描述
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
图示两个链表在节点 c1 开始相交:
(2)题目分析
- 第一步:判断两个链表是否相交(遍历两个链表,看尾指针是否指向同一个地方,是的话就相交)
- 第二步:若相交,找出第一个交点
具体代码实现如下
typedef struct ListNode ListNode;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
//1.判断是否相交
ListNode*ptail1=headA;
ListNode*ptail2=headB;
int lenA=1;
int lenB=1;
while(ptail1)
{
ptail1=ptail1->next;
lenA++;
}
while(ptail2)
{
ptail2=ptail2->next;
lenB++;
}
if(ptail1!=ptail2)
{
return NULL;
}
//2.找相交的节点
ListNode* _long=headA;
ListNode* _short=headB;
if(lenA>=lenB)//假设法,经过此处处理之后就可以不用关心到底是A链表长还是B链表长
{
_long=headA;//此处保证了long指向了较长的链表
_short=headB;
}
else
{
_long=headB;
_short=headA;
}
int gap=abs(lenA-lenB);
ListNode* p1=_long;
ListNode* p2=_short;
while(gap--)
{
p1=p1->next;
}
while(p1&&p2)
{
if(p1==p2)
{
return p1;
}
else
{
p1=p1->next;
p2=p2->next;
}
}
return NULL;
}