题目链接:876. 链表的中间结点 - 力扣(LeetCode)
我们先看题目描述:
我们用画图用快慢指针来解决这个问题
定义一个快指针fast,一个慢指针slow
快指针一次走两个结点,慢指针一次走一个结点
当快指针指向NULL或者快指针的下一个结点指向NULL的时候,慢指针刚好走到中间结点
有了这个思路,那我们就可以开始写代码了
struct ListNode* middleNode(struct ListNode* head)
{
struct ListNode* fast=head,*slow=head;
while( fast && fast->next )
{
slow=slow->next;
fast=fast->next->next;
}
return slow;
}
题目链接:203. 移除链表元素 - 力扣(LeetCode)
我们定义一个cur指向当前结点,定义prev指向前一个结点,next指向下一个结点
如果cur->val==val,那我们就删除这个结点
怎么删除呢:
我们让prev->next指向cur->next,然后free(cur)
为了防止野指针,我们可以定义一个next指向cur->next,先free(cur),再让prev->next指向next
如果cur为第一个结点,那prev就是空,我们在这里得分成两种情况:
如果prev不为空,则prev->next=next;
如果prev为空,head=next;
由于链表是无序的,因此我们需要遍历一遍才能删除所有的val,可以使用while循环来控制
根据解题思路,我们可以写代码了:
struct ListNode* removeElements(struct ListNode* head, int val) {
struct ListNode* prev=NULL;
struct ListNode* cur=head;
while(cur!=NULL)
{
if(cur->val==val)
{
struct ListNode* next=cur->next;
free(cur);
if(prev)
{
prev->next=next;
}
else
{
head=next;
}
cur=next;
}
else
{
prev=cur;
cur=cur->next;
}
}
return head;
}
我们可以设计算法让整个链表掉头
定义三个代码n1,n2,n3
n1指向NULL,n2指向head,n3指向第二个结点
当n2不为NULL的时候,让n2->next反方向指向n1,然后n1,n2,n3都往后移动
当n3走到NULL的时候,会出现空指针的问题,这个时候我们给n3单独加一个判断
if(n3!=NULL),n3=n3->next
注意:我们没有交换值,而是改变了指针的指向
另外,当链表本身为空的时候,直接返回NULL,所以我们也需要加一个判断链表为空的条件
定义一个newhead指向NULL作为新链表的头,定义cur指向原链表的第一个结点,next保存第二个结点
然后将cur尾插,newhead作为新的头,cur走到原链表的第二个结点,next指向next->next
最后,当cur不为空的时候,则进入循环,为了防止next成为空指针,我们加一个判断:
if(next!=NULL),则next=next->next
同样的,当链表本身为空的时候,直接返回NULL,所以我们也需要加一个判断链表为空的条件
根据思路一,我们可以写出下面的代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* reverseList(struct ListNode* head) {
if(head==NULL)
{
return NULL;
}
struct ListNode* n1=NULL;
struct ListNode* n2=head;
struct ListNode* n3=n2->next;
while(n2)
{
n2->next=n1;
n1=n2;
n2=n3;
if(n3)
n3=n3->next;
}
free(n3);
free(n2);
return n1;
}
根据思路二,我们可以写出下面的代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* reverseList(struct ListNode* head) {
if(head==NULL)
{
return NULL;
}
struct ListNode* newhead=NULL;
struct ListNode* cur=head;
struct ListNode* next=cur->next;
while(cur)
{
cur->next=newhead;
newhead=cur;
cur=next;
if(next)
next=next->next;
}
free(next);
free(cur);
return newhead;
}
我们先要搞清楚一个概念,单链表可以相交,但绝对不会交叉
原因如下:
单链表中,多个结点可以存一个结点的地址,但是一个结点不可能存多个结点的地址
因为每个结点只有一个next
所以链表的相交一定是Y字形的
这里我们要做的有两步:
暴力求解:A链表的所有结点依次去B链表找一遍
注意:一定要用结点的地址去比对
分别找尾结点,尾结点地址相同就相交
算出两个链表的长度,得出两个链表的长度差,让长链表先走差距步,然后同时走,当第一个地址相同的时候,这就是交点
这个算法的时间复杂度是F(3N),即O(N)
我们先定义curA,curB分别指向两个链表,用while循环求长度,并判断是否相交(只有相交了才会有交点)如果不相交则返回NULL,相交再进行下一步
我们得到lenA和lenB,用C语言自带的函数abs求出差距的绝对值
int n=abs(lenA-lenB);
那么谁先走呢,我们用假设法:假设A长B短
如果假设错误,那就纠正过来
让长的先走差距步
然后同时走,想等的时候返回任意一个地址就是交点
根据思路二,我们写出代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
struct ListNode *curA=headA,* curB=headB;
int lenA=1,lenB=1;
while(curA->next)
{
lenA++;
curA=curA->next;
}
while(curB->next)
{
lenB++;
curB=curB->next;
}
if(curA!=curB)
{
return NULL;
}
int n=abs(lenA-lenB);
struct ListNode *longList=headA, *shortList=headB;
if(lenB>lenA)
{
longList=headB;
shortList=headA;
}
while(n--)
{
longList=longList->next;
}
while(longList!=shortList)
{
longList=longList->next;
shortList=shortList->next;
}
return longList;
}
我们先了解一个知识:循环链表
尾结点不指向NULL,指向头就是循环链表
那么带环链表就意味着尾结点的next可以指向链表的任意一个结点,甚至可以指向他自己
这里我们的算法思路唯一靠谱的还是快慢指针
slow一次走一步,fast一次走两步,当slow走到中间的时候,fast一定入环了,如果fast指向NULL,则该链表无环
当slow再走一半也就入环了,这个时候,由于slow走的慢,fast走的快,所以fast和slow最终会相遇的
那我们的代码就应该是
如果fast或者fast->next为NULL则返回false
当fast或者fast->next不为NULL的时候,slow走一步,fast走两步,当fast==slow,则返回true
根据思路我们就可以写代码了:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
bool hasCycle(struct ListNode *head) {
struct ListNode *fast=head,*slow=head;
while(fast&&fast->next)
{
slow=slow->next;
fast=fast->next->next;
if(slow==fast)
{
return true;
}
}
return false;
}
题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
我们假设起点到环的入口点的距离是L,入口点到相遇点的距离是X,环的长度是C
那么画图我们可以得知:
(n:slow进环前,fast已经在环里走了 n 圈)
由于fast=slow*2,所以我们可以得到结论:一个指针从相遇点开始走,一个指针从头开始走,最终会在入口点相遇
我们可以画图来证明一下:
根据这个思路我们可以写出代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *detectCycle(struct ListNode *head) {
struct ListNode *slow,* fast;
slow=fast=head;
while(fast&&fast->next)
{
slow=slow->next;
fast=fast->next->next;
if(slow==fast)//相遇
{
struct ListNode *meet=slow;
while(head!=meet)
{
head=head->next;
meet=meet->next;
}
return meet;
}
}
return NULL;
}
题目链接:21. 合并两个有序链表 - 力扣(LeetCode)
这个算法思路很简单:就是直接找小尾插
定义一个tail和head,对比两个链表结点的val,小的尾插到tail->next,如果一个链表先走完,就把另外一个链表尾插到tail->next,最后返回head就行
具体的流程就是:
有一个特殊情况就是:如果list1和list2有一个为空的话,那就直接返回另外一个链表
有了思路,我们就可以写代码了:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
if(list1==NULL)
return list2;
if(list2==NULL)
return list1;
struct ListNode*tail=NULL,*head=NULL;
while(list1&&list2)
{
if(list1->val<list2->val)
{
if(tail==NULL)
head=tail=list1;
else
{
tail->next=list1;
tail=tail->next;
}
list1=list1->next;
}
else
{
if(tail==NULL)
head=tail=list2;
else
{
tail->next=list2;
tail=tail->next;
}
list2=list2->next;
}
}
if(list1)
tail->next=list1;
if(list2)
tail->next=list2;
return head;
}
题目链接:138. 随机链表的复制 - 力扣(LeetCode)
这个题目很长,但是意思其实很简单:就是一个单链表,每个结点多了一个指针random随机指向链表中的任意结点或者NULL,我们血需要复制这个链表,连同random一起复制下来
思路一是我们用cur遍历链表,用for循环找random对应的原链表中的random,这个算法找每个random的时间复杂度都是O(N),整个算法的时间复杂度是O(N^2)
思路二是分三步走:
这个思路更优,时间复杂度是O(N)
我们可以简单画图来演示一下这三步:
定义cur遍历链表,用malloc开辟一个copy的空间,然后依次将cur->val赋给copy->val,接着将copy结点插入到cur和cur->next之间,cur走到copy-.>next
让cur回到head,经过第一步之后我们知道copy就是cur->next,这里我们分为两种情况:
然后cur走到cur->next->next
最后一步就是尾插,定义newhead和tail先指向NULL,cur回到head,copy是cur->next,next保存copy->next 将cpoy尾插到tail,将cur->next接到next恢复原链表,最后返回newhead
我们根据思路二来写代码:
/**
* Definition for a Node.
* struct Node {
* int val;
* struct Node *next;
* struct Node *random;
* };
*/
struct Node* copyRandomList(struct Node* head) {
//copy结点插入到原结点的后面
struct Node*cur=head;
while(cur)
{
struct Node*copy=(struct Node*)malloc(sizeof(struct Node));
copy->val=cur->val;
copy->next=cur->next;
cur->next=copy;
cur=cur->next->next;
}
//处理copy结点的random
cur=head;
while(cur)
{
struct Node*copy=cur->next;
if(cur->random==NULL)
{
copy->random=NULL;
}
else
{
copy->random=cur->random->next;
}
cur=cur->next->next;
}
//copy结点拆下来尾插
cur=head;
struct Node*newhead=NULL,*tail=NULL;
while(cur)
{
struct Node*copy=cur->next;
struct Node*next=copy->next;
if(tail==NULL)
{
newhead=tail=copy;
}
else
{
tail->next=copy;
tail=tail->next;
}
cur->next=next;
cur=next;
}
return newhead;
}
链表分为两种:带头结点的和不带头结点的
之前我们学习了不带哨兵位的单链表,并实现了相关代码
现在我们认识一下带哨兵位头结点的单链表:
plist指向带哨兵位的头结点
这个结点不存储有效数据
如果为空链表:
带哨兵位的单链表也有他自己的优势,我们用一道题来证明一下:
假设有下面这个链表,给定数字5
那么排序后就是这样,不改变原来的数据顺序
可以分割成两个链表,plist1和plist2,小于x的结点尾插到plist1,否则尾插到plist2
当链表的数据全部尾插结束后,将plist2头结点尾插到plist1,这个时候带哨兵位的头结点就非常有优势了
我们可以定义两个头结点head和head2,分别作为两个链表的哨兵位,再定义两个尾结点tail1和tail2方便两个链表的尾插,定义cur遍历链表,和x值做比较
我们思路清晰了就可以写代码了:
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};*/
#include <cstdlib>
class Partition {
public:
ListNode* partition(ListNode* pHead, int x) {
// write code here
struct ListNode* head1,* head2,* tail1,* tail2;
head1=(struct ListNode*)malloc(sizeof(struct ListNode));
head2=(struct ListNode*)malloc(sizeof(struct ListNode));
tail1=head1;
tail2=head2;
struct ListNode* cur=pHead;
while(cur)
{
if(cur->val<x)
{
tail1->next=cur;
tail1=tail1->next;
}
else
{
tail2->next=cur;
tail2=tail2->next;
}
cur=cur->next;
}
tail1->next=head2->next;
tail2->next=NULL;
pHead=head1->next;
free(head1);
free(head2);
return pHead;
}
};
由于该题只能用c++,但是我们可以完全按照C语言的语法规则写内部,最后就可以通过了;
题目链接:链表中倒数第k个结点_牛客题霸_牛客网 (nowcoder.com)
我们可以利用快慢指针来解决问题:
先让fast走k步,这时候fast和slow之间的距离就是k,然后让fast和slow同时同步往后走,当fast走到NULL的时候,slow就指向了倒数第k个结点了
while(k--)就是走k步
先让fast走k-1步,这时候fas->next和slow之间的距离就是k,然后让fast和slow同时同步往后走,当fast->next走到NULL的时候,slow就指向了倒数第k个结点了
while(--k)就是走k-1步,由于fast走到NULL就没有fast->next了,这里我们需要加一个判断:
fast或者fast->next为NULL就返回NULL
根据思路一,我们可以写出代码一
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
/**
*
* @param pListHead ListNode类
* @param k int整型
* @return ListNode类
*/
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
struct ListNode* fast = pListHead, *slow = pListHead;
while(k--)
{
if(fast==NULL)
{
return NULL;
}
fast=fast->next;
}
while(fast)
{
slow=slow->next;
fast=fast->next;
}
return slow;
}
根据思路二,我们可以写出代码二
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
/**
*
* @param pListHead ListNode类
* @param k int整型
* @return ListNode类
*/
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
struct ListNode* fast = pListHead, *slow = pListHead;
while(--k)
{
if(fast==NULL||fast->next==NULL)
{
return NULL;
}
fast=fast->next;
}
while(fast->next)
{
slow=slow->next;
fast=fast->next;
}
return slow;
}
题目链接:链表的回文结构_牛客题霸_牛客网 (nowcoder.com)
我们的思路是:
我们可以简单画个图来表示一下:
‘
奇数和偶数都是可以的
我们可以用快慢指针来找中:leetcode:链表的中间结点-CSDN博客
写一个找中的函数middleNode:
然后写一个逆置的函数reverseList:
我们画图表示一下头插的过程:
最后我们进行一个对比
有了这个思路,我们就可以编写代码了:
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};*/
class PalindromeList {
public:
struct ListNode*reverseList(ListNode*head)
{
struct ListNode*cur=head;
struct ListNode*newhead=NULL;
while(cur)
{
struct ListNode*next=cur->next;
//头插
cur->next=newhead;
newhead=cur;
cur=next;
}
return newhead;
}
struct ListNode*middleNode(ListNode*head)
{
struct ListNode*slow,*fast;
slow=fast=head;
while(fast&&fast->next)
{
slow=slow->next;
fast=fast->next->next;
}
return slow;
}
bool chkPalindrome(ListNode* head) {
// write code here
struct ListNode*mid=middleNode(head);
struct ListNode*rhead=reverseList(mid);
while(head&&rhead)
{
if(head->val!=rhead->val)
{
return false;
}
head=head->next;
rhead=rhead->next;
}
return true;
}
};