介绍
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表 中的指针链接次序实现的 。
链表的出现是为了弥补顺序表的不足而出现的,链表的特点就像一条大金链子,增删较方便。
声明
//SingleList单链表
typedef int DataType;
typedef struct SListNode
{
DataType data;
struct SListNode* next;
}SLNode;
//双向循环链表
typedef struct ListNode
{
DataType data;
struct ListNode* prev;
struct ListNode* next;
}ListNode;
OJ
1.反转链表
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
算法思路:利用三个指针进行指向的修改,curNext记录结点cur下一个结点,prev用来记录结点cur上一个结点,修改当前结点cur指向prev,修改完cur指向后,prev指向当前cur结点,之后cur指向下一个结点,curNext指向下一个结点,依次遍历链表。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* reverseList(struct ListNode* head) {
//0个结点或1个结点
if(head==NULL||head->next==NULL)
return head;
//多个结点
struct ListNode* prev = NULL,*cur = head,*curNext = cur->next;
while(cur)
{
//修改当前结点指向
cur->next = prev;
//迭代链表
prev = cur;
cur = curNext;
if(curNext)
curNext = curNext->next;
}
return prev;
}
法二:
struct ListNode* reverseList(struct ListNode* head){
// if(head==NULL||head->next==NULL)
// return head;
//头插
struct ListNode* newhead = NULL,*cur = head;
while(cur)
{
struct ListNode* curNext = cur->next;
cur->next = newhead;
newhead=cur;
cur=curNext;
}
return newhead;
}
2.移除链表元素
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
示例 1:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode Node;
struct ListNode* removeElements(struct ListNode* head, int val) {
Node* cur = head;
Node* prev = NULL;
while(cur)
{
if(cur->val==val)
{
//注意:第一个结点就是val
if(prev==NULL)
{
head=cur->next;
free(cur);
cur=head;
}
else
{
prev->next=cur->next;
free(cur);
cur=prev->next;
}
}
else
{
prev = cur;
cur = cur->next;
}
}
return head;
}
3.链表的中间结点
给你单链表的头结点 head
,请你找出并返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
示例 1:
输入:head = [1,2,3,4,5]
输出:[3,4,5]
解释:链表只有一个中间结点,值为 3 。
示例 2:
输入:head = [1,2,3,4,5,6]
输出:[4,5,6]
解释:该链表有两个中间结点,值分别为 3 和 4 ,返回第二个结点。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode Node;
struct ListNode* middleNode(struct ListNode* head) {
Node* slow=head,*fast = head;
while(fast&&fast->next)
{
slow=slow->next;
fast=fast->next->next;
}
return slow;
}
4.输出链表中倒数第k个结点
实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。
注意:本题相对原题稍作改动
示例:
输入: 1->2->3->4->5 和 k = 2
输出: 4
说明:
给定的 k 保证是有效的。
算法思路:双指针思想,利用快慢指针的相位差为k,来进行遍历,初始状态快慢指针都指向头结点,然后让快指针先走k个结点,慢指针还在头结点,此时快慢指针刚好相差k个结点,之后快慢指针同步往下一个结点访问,当快指针遍历到NULL,慢指针刚好指向倒数第k个结点。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode Node;
int kthToLast(struct ListNode* head, int k) {
Node* slow=head,*fast = head;
//fast先走k位
while(k--)
{
if(fast)
{
fast=fast->next;
}
}
while(fast)
{
slow = slow->next;
fast = fast->next;
}
return slow->val;
}
5.合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
示例 2:
输入:l1 = [], l2 = []
输出:[]
示例 3:
输入:l1 = [], l2 = [0]
输出:[0]
算法思路:首先取两个链表中较小的元素作为新链表的头节点,然后通过比较两个链表中较小的元素链接到新链表中,注意:带哨兵位的头结点,只是开辟了一个空间,不存放元素,返回的是头结点的下一个结点。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode Node;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
if(list1==NULL)
return list2;
if(list2==NULL)
return list1;
//不带哨兵位的头节点
// Node* head=NULL,*cur=NULL;
// if((list1->val)<(list2->val))
// {
// head = list1;
// list1=list1->next;
// }
// else
// {
// head = list2;
// list2=list2->next;
// }
// cur = head;
//带哨兵位的头节点
Node* head=(Node*)malloc(sizeof(Node));
Node* cur = head;
while(list1&&list2)
{
if(list1->val<list2->val)
{
cur->next=list1;
list1=list1->next;
}
else
{
cur->next=list2;
list2=list2->next;
}
cur=cur->next;
}
if(list1)
{
cur->next = list1;
}
if(list2)
{
cur->next = list2;
}
//带哨兵位,取哨兵位的下一个做头节点
Node* realHead = head->next;
free(head);
return realHead;
}
6.链表分割
以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前给定一个链表的头指针 ListNode* head,请返回重新排列后的链表的头指针。注意:分割以后保持原来的数据顺序不变。
算法思路:通过两个带哨兵位的头结点,分别链接比给定值x小的元素给lessHead,大于等于x的元素存放在greaterHead中,最后把两个链表链接在一块。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode Node;
struct ListNode* partition(struct ListNode* head, int x) {
//声明两个带哨兵位的头结点
Node* lessHead = (Node*)malloc(sizeof(Node));
Node* lessTail = lessHead;
Node* greaterHead = (Node*)malloc(sizeof(Node));
Node* greaterTail = greaterHead;
lessHead->next = NULL;
greaterHead->next = NULL;
Node* cur = head;
while (cur)
{
if (cur->val < x)
{
lessTail->next = cur;
lessTail = cur;
}
else
{
greaterTail->next = cur;
greaterTail = cur;
}
cur = cur->next;
}
lessTail->next = greaterHead->next;
greaterTail->next = NULL;//注意:一定要把最后一个指向NULL,不然可能变成环
free(greaterHead);
head = lessHead->next;
free(lessHead);
return head;
}
7.回文链表
给定一个链表的 头节点 head
,请判断其是否为回文链表。
如果一个链表是回文,那么链表节点序列从前往后看和从后往前看是相同的。
示例 1:
输入: head = [1,2,3,3,2,1]
输出: true
示例 2:
输入: head = [1,2]
输出: false
算法思路:利用快慢指针找到中间位置,然后将其在中间位置进行断链,慢指针的前驱置为NULL,拆分成两个链表,将后半段链表逆置后得到newHead,然后让head和newHead链表的元素进行一对一比较,有一个不相等返回false,都相等返回true。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode Node;
//链表逆置
struct ListNode* reverseLinked(struct ListNode* head)
{
if(head==NULL|| head->next == NULL)
return head;
//头插
Node* cur = head->next;
head->next = NULL;
Node* tail = cur->next;
while(cur)
{
cur->next = head;
head = cur;
cur = tail;
//注意空指针
if(tail)
{
tail = tail->next;
}
}
return head;
}
bool isPalindrome(struct ListNode* head) {
Node* slow,*fast;
slow = fast = head;
Node* prev = NULL;
while(fast && fast->next)
{
prev = slow;
slow = slow->next;
fast = fast->next->next;
}
//断链 拆分两段
if(prev)//注意空指针
{
prev->next = NULL;
}
//逆置后半段
Node* newHead = reverseLinked(slow);
Node* cur = head;
while(cur)
{
if(cur->val == newHead->val)
{
newHead = newHead->next;
cur = cur->next;
}
else
{
return false;
}
}
return true;
}
8.相交链表
给定两个单链表的头节点 headA
和 headB
,请找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null
。
图示两个链表在节点 c1
开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
示例 2:
输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at '2'
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例 3:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。
算法思想:首先找出两链表较长的一个longList,利用两链表长度的差值k,把longList向下移动k个结点,此时以longList和shortList为头指针的链表长度相等,遍历这两个链表一次比较结点的地址,第一次相等的结点就是两链表相交起始结点。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode Node;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
//找出最长链表
int lenA = 0,lenB = 0;
Node *curA = headA, *curB = headB;
while(curA)
{
curA = curA->next;
lenA++;
}
while(curB)
{
curB = curB->next;
lenB++;
}
Node* longList = headA;
Node* shortList= headB;
if(lenA<lenB)
{
longList = headB;
shortList = headA;
}
//两链表长度差值
int k = abs(lenA-lenB);
while(k>0)
{
longList = longList->next;
k--;
}
//此时两链表长度相等,比较结点地址
while(longList)
{
if(longList == shortList)
{
return longList;
}
longList = longList->next;
shortList = shortList->next;
}
return NULL;
/*
//两个链表长度差值
int k = abs(lenA-lenB);
if(lenA>lenB)
{
while(k>0&&headA)
{
headA = headA->next;
k--;
}
}else
{
while(k>0&&headB)
{
headB = headB->next;
k--;
}
}
while(headA)
{
if(headA==headB)
{
return headA;
}
headA = headA->next;
headB = headB->next;
}
return NULL;
*/
}
9.环形链表
给你一个链表的头节点 head
,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos
不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true
。 否则,返回 false
。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
算法思路:利用快慢指针的原理,如果链表中有环,那么快指针以2步速度,去追慢指针以1步速度,最终快慢指针相遇就说明链表中有环,类似于在操场上跑步,你以2m/s速度同向去追一个1m/s的小伙伴,最终肯定能够在整数秒内追上。(注意:只能是2步速度去追,不然你们两个之间距离X不会为0)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode Node;
bool hasCycle(struct ListNode *head) {
//快慢指针
Node* slow ,*fast;
slow = fast = head;
while(fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
//注意:开始slow和fast都是head
if(slow == fast)
{
return true;
}
}
return false;
}
10.随机链表的复制
给你一个长度为 n
的链表,每个节点包含一个额外增加的随机指针 random
,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n
个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next
指针和 random
指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
例如,如果原链表中有 X
和 Y
两个节点,其中 X.random --> Y
。那么在复制链表中对应的两个节点 x
和 y
,同样有 x.random --> y
。
返回复制链表的头节点。
用一个由 n
个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index]
表示:
val
:一个表示Node.val
的整数。random_index
:随机指针指向的节点索引(范围从0
到n-1
);如果不指向任何节点,则为null
。
你的代码 只 接受原链表的头节点 head
作为传入参数。
示例 1:
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
示例 2:
输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]
算法思路:首先把复杂链表的val依次拷贝到每个结点后面链接的新结点中,然后找到每个原结点的random,random指向的下一个结点就是该拷贝结点random所指向的结点,依次进行拷贝结点random,最后把原链表和拷贝链表拆分,返回拷贝链表头指针copyHead。
/**
* Definition for a Node.
* struct Node {
* int val;
* struct Node *next;
* struct Node *random;
* };
*/
typedef struct Node Node;
struct Node* copyRandomList(struct Node* head) {
if(head == NULL)
return NULL;
Node* cur = head;
Node* next = cur->next;
//拷贝val
while(cur)
{
Node* copy = (Node*)malloc(sizeof(Node));
copy->val = cur->val;
copy->next = next;
copy->random = NULL;
cur->next = copy;
cur = next;
if(next)
next= next->next;
}
//拷贝random
cur = head;
Node* curCopy = cur->next;
while(cur)
{
if(cur->random)
curCopy->random = cur->random->next;
else
curCopy->random = NULL;
cur = curCopy->next;
if(cur)
curCopy = cur->next;
}
//拆分拷贝链表
cur = head;
Node* copyHead = cur->next;
curCopy = cur->next;
while(cur)
{
next = curCopy->next;
cur->next = next;
cur = next;
if(next)
{
curCopy->next = next->next;
curCopy = next->next;
}
}
return copyHead;
}
11.对链表进行插入排序
给定单个链表的头 head
,使用 插入排序 对链表进行排序,并返回 排序后链表的头 。
插入排序 算法的步骤:
- 插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
- 每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
- 重复直到所有输入数据插入完为止。
下面是插入排序算法的一个图形示例。部分排序的列表(黑色)最初只包含列表中的第一个元素。每次迭代时,从输入数据中删除一个元素(红色),并就地插入已排序的列表中。
对链表进行插入排序。
示例 1:
输入: head = [4,2,1,3]
输出: [1,2,3,4]
示例 2:
输入: head = [-1,5,3,4,0]
输出: [-1,0,3,4,5]
算法思路:插入排序是在一段有序区间内进行插入新的结点,开始时第一个结点有序,判断下一个结点是头插还是尾插,然后在下一个结点就可能是头插,尾插,中间插,三种情况讨论,分清楚思路后进行迭代。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode Node;
struct ListNode* insertionSortList(struct ListNode* head) {
if(head==NULL|| head->next == NULL)
return head;
//排序后的头结点
// //注意:看下面这句代码。。。
// Node* sortHead = head;
// sortHead->next = NULL;
Node* sortHead = head;
Node* cur = head->next;
sortHead->next = NULL;
Node* next = NULL;
//在有序的区间内进行插入
//头插,中间插,尾插
while(cur)
{
next = cur->next;
//头插
if(cur->val<=sortHead->val)
{
cur->next = sortHead;
sortHead = cur;
}
else
{
//中间插 [2,4,6] cur=3
Node* sortCur = sortHead->next;
Node* sortPrev = sortHead;
while(sortCur)
{
if(cur->val<=sortCur->val)
{
sortPrev->next = cur;
cur->next = sortCur;
break;//注意:插入成功后跳出循环
}
else
{
sortPrev = sortCur;
sortCur = sortCur->next;
}
}
//尾插
if(sortCur == NULL)
{
sortPrev->next = cur;
cur->next = NULL;
// sortCur = cur;
// sortCur->next = NULL;//注意尾插后下一个要置为NULL,不然会成环
}
}
cur = next;
}
return sortHead;
}
12.删除链表中重复元素II
给定一个已排序的链表的头 head
, 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。
示例 1:
输入:head = [1,2,3,3,4,4,5]
输出:[1,2,5]
示例 2:
输入:head = [1,1,1,2,3]
输出:[2,3]
算法思路:利用快慢指针,当慢指针cur的值与快指针next的值不同时,prev记录cur前驱,cur等于next,next向下走一步;当慢指针cur的值与快指针next的值相同时,cur不动,next一直向下遍历,直到出现一个next所指的值与cur不相等时,删除cur到当前next指向之间所有的重复结点,然后cur指向next,next指针继续向下,prev指向cur使之链接,继续判断慢指针cur的值和快指针next的值是否相同,重复上述步骤进行迭代。直到next为NULL,链表遍历完跳出循环,返回头指针。(注意:头部和尾部细节处理,prev为空,next为空)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode Node;
struct ListNode* deleteDuplicates(struct ListNode* head) {
if(head==NULL|| head->next==NULL)
return head;
//快慢指针
Node* cur = head;
Node* next = cur->next;
Node* prev = NULL;
//遍历链表
while(next)
{
//前后指针不相等,prev cur next 三指针同时往下走
if(cur->val != next->val)
{
prev = cur;
cur = next;
next = next->next;
}
else
{
//找到下一个不相等的结点 条件&&
while(next && cur->val == next->val)
next= next->next;
//删除重复出现结点
while(cur!=next)
{
Node* del = cur;
cur = cur->next;
free(del);
}
//细节处理,prev为空时
if(prev)
prev->next = cur;
else
head = cur;
//细节处理,next已经走到最后
if(next)
next = next->next;
}
}
return head;
}