目录
链表分割
1.题目链接
2.题目描述
现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针
3.题目分析
由于他说不能改变原有顺序,因此就想着先搞两条新链表,一边遍历原链表,一边将小于x的放入链表lower,其他的就放入链表greater,最后将lower连接在greater之前即可
4.代码实现
ListNode* partition(ListNode* pHead, int x) {
//初始化
struct ListNode* lowerhead,*lowertail,* greaterhead,* greatertail;
lowerhead=(struct ListNode*)malloc(sizeof(struct ListNode));
greaterhead=(struct ListNode*)malloc(sizeof(struct ListNode));
struct ListNode* cur=pHead;
lowertail=lowerhead;greatertail=greaterhead;
lowertail->next=nullptr;greatertail->next=nullptr;
//遍历
while(cur!=nullptr){
if(cur->val<x){ //放入lower
lowertail->next=cur;
lowertail=lowertail->next;
}
else{ //放入greater
greatertail->next=cur;
greatertail=greatertail->next;
}
cur=cur->next;
}
greatertail->next=nullptr;
lowertail->next=greaterhead->next; //连接
pHead=lowerhead->next;
//释放这两条链表的表头
free(lowerhead);
free(greaterhead);
return pHead;
}
链表的中间结点
1.题目链接
2.题目描述
给你单链表的头结点 head
,请你找出并返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点
3.题目分析
做这道题前,我们需要先知道快慢指针算法,其应用范围很广
- 处理链表或数组的循环问题
- 找链表中点或需要知道特定元素的位置
快指针每次走两步,慢指针每次走一步,你可以画图感受一下~
- 如果有偶数个结点,当快指针指向空时,慢指针恰好指向第二个中间结点
- 如果有奇数个结点,当快指针的next指向空时,慢指针恰好指向中间结点
4.代码实现
struct ListNode* middleNode(struct ListNode* head){
struct ListNode* slow=head,*fast=head;
while(fast!=NULL&&fast->next!=NULL){
slow=slow->next;
fast=fast->next->next;
}
return slow;
}
合并链表
1.题目链接
2.题目描述
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的
3.题目分析
直接定义一个新头结点,之后按照数值大小轮流遍历原先的两个链表
4.代码实现
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
struct ListNode* head=NULL,*t=NULL; //定义新的头结点
//防止传入的是空结点
if(list1==NULL){
return list2; //如果list1是空,就返回list2,不管list2是否为空
}
else if(list2==NULL){
return list1; //同理
}
else{
//确定头结点使用谁的结点
if (list1->val <= list2->val) {
head=list1;
list1=list1->next;
}
else{
head=list2;
list2=list2->next;
}
//轮流遍历
t=head; //t替新头结点进行连接
while(list1!=NULL&&list2!=NULL){ //有一方遍历完后,就结束循环
if(list1->val<=list2->val){ //谁小谁就连接在t之后
t->next=list1;
t=list1;
list1=list1->next;
}
else{
t->next=list2;
t=list2;
list2=list2->next;
}
}
while(list1){ //如果list1还未遍历完,就将剩下的结点直接连接在t之后
t->next=list1;
t=list1;
list1=list1->next;
}
while(list2){ //同理
t->next=list2;
t=list2;
list2=list2->next;
}
t->next=NULL; //别忘了将新链表的尾结点指向空
return head;
}
}
反转链表
1.题目链接
2.题目描述
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表
3.题目分析(一定要画图哟!)
初始化指针
- newhead不断向后走,最终它停在尾结点上
- prev来保存上一个结点,通过改变newhead和prev的连接来实现反转
- 通过next来保存原链表的下一个结点,防止反转连接关系后找不到原结点
经过一轮循环后,第一个和第二个结点的连接成功反转,之后就不断循环该过程,直到next指向空,就完成遍历链表啦~
4.代码实现
struct ListNode* reverseList(struct ListNode* head){
if(head==NULL){
return head;
}
else{
struct ListNode* newhead = head, * prev = head, * next = head->next;
prev->next = NULL; //注意这里!!!一定要把第一个结点置空,因为该结点时反转链表的最后一个结点,如果不置空,遍历反转链表时会出现问题
while (next != NULL) {
//使两个指针往后走
newhead = next;
next = next->next;
//改变指向关系
newhead->next = prev;
//使prev往后走
prev = newhead;
}
return newhead;
}
}
链表中倒数第k个结点
1.题目链接
链表中倒数第k个结点_牛客题霸_牛客网 (nowcoder.com)
2.题目描述
输入一个链表,输出该链表中倒数第k个结点
3.题目分析
这里用到了一点数学知识,就很巧妙的先让fast跑k次,之后继续遍历fast,直到fast指向空(该循环便循环(链表长度-k)次了),因此slow就指向了倒数第k个结点
4.代码实现
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k) {
struct ListNode* slow = pListHead, * fast = pListHead;
if (pListHead == NULL) { //防止传入的指针是空
return NULL;
}
while (k--) { //这里不用判断k==0的情况,因为=0时不会进入循环
if (fast == NULL) { //如果链表长度不够k,直接返回,防止访问出现错误
return NULL;
}
fast = fast->next;
}
while (fast!= NULL) {
slow = slow->next;
fast = fast->next;
}
return slow;
}
回文结构
1.题目链接
2.题目描述
对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。
给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。
3.题目分析
判断回文,就是需要将该链表分成两部分,将其中一部分的链表反转(前面实现过哦),然后判断两部分的结点数值是否相同
而分成两部分就应该找到中间结点,如果为偶数个结点,就找到它第二个结点(前面实现过哦)
4.代码实现
struct ListNode* findmid(struct ListNode* head){
struct ListNode* slow=head,*fast=head;
while(fast!=nullptr&&fast->next!=nullptr){
slow=slow->next;
fast=fast->next->next;
}
if(fast==nullptr){
return slow;
}
slow=slow->next;
return slow;
}
struct ListNode* reverse(struct ListNode* p){
struct ListNode*prev=p,*next=p->next,*newhead=p;
newhead->next=nullptr;
while(next!=nullptr){
newhead=next;
next=next->next;
newhead->next=prev;
prev=newhead;
}
return newhead;
}
bool chkPalindrome(ListNode* A) {
struct ListNode* mid=findmid(A); //找到中间结点
struct ListNode* remid=reverse(mid); //反转包括中间结点之后的链表
while(remid){ //将反转后的链表进行遍历
if(A->val!=remid->val){ //与传进来的A进行比较
return false;
}
A=A->next;
remid=remid->next;
}
return true;
}
环形链表(一)--判断
1.题目链接
2.题目描述
给你一个链表的头节点 head ,判断链表中是否有环, 如果链表中存在环 ,则返回 true 。 否则,返回 false
3.题目分析
这里又要用到快慢指针啦!
因为fast走的快,但如果遍历的过程中,slow超过了fast,则证明存在环
4.代码实现
bool hasCycle(struct ListNode *head) {
if(head==NULL||head->next==NULL){ //防止传入的是空指针||只有一个结点(一个结点不能成环)
return false;
}
struct ListNode *slow=head,*fast=head;
while(slow<=fast){
if(fast==NULL||fast->next==NULL){ //中途如果fast指向空,则说明没有环
return false;
}
slow=slow->next;
fast=fast->next->next;
}
return true;
}
环形链表(二)--返回入环的第一个节点
1.题目链接
2.题目描述
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。不允许修改 链表。
3.题目分析
看的题解哈~
- 这种问题一般都要用快慢指针,当fast和slow相遇时:此时fast比slow恰好多走了一个环的距离(可以自己画个带环链表比划比划),由于fast每次走两步,slow走一步,因此fast步数=2*slow步数
- 当slow再走 从头结点到入环第一个结点 的距离的话,slow就恰好指向入环的第一个结点了
- 那么,如何得到这段距离呢?就再使用双指针,当新指针与slow相遇时,就说明他俩都已经走了这段距离了
- slow-新指针=c环
4.代码实现
struct ListNode *detectCycle(struct ListNode *head) {
if(head==NULL||head->next==NULL){ //防止传入的是空指针||只有一个结点(一个结点不能成环)
return NULL;
}
struct ListNode *slow=head,*fast=head;
while(1){
if(fast==NULL||fast->next==NULL){ //如果fast可以指向空,说明没有环
return NULL;
}
slow=slow->next;
fast=fast->next->next;
if(slow==fast){ //相遇时跳出循环
break;
}
}
struct ListNode *t=head; //新指针
while(t!=slow){
t=t->next;
slow=slow->next;
}
return t;
}
移除链表元素
1.题目链接
2.题目描述
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点
3.题目分析
- 先将head遍历到第一个数值!=val的结点,防止头结点开始就应该删除
- 定义一个p指针作为head的副本,将p遍历,寻找数值不等于val的结点进行连接,以及将等于的结点释放掉
4.代码实现
struct ListNode* removeElements(struct ListNode* head, int val){
while(head!=NULL&&head->val==val){ //找到适合的新链表起点
head=head->next;
}
struct ListNode* p=head;
while(p!=NULL&&p->next!=NULL){
//这里如果不设置p->next!=NULL,那么在下面的代码中,由于会访问p->next->val
//如果p->next为空,则会发生访问错误
if(p->next->val==val){ //通过p查找p的下一个结点是否数值==val
struct ListNode* q=p->next; //q此时为要删除的结点
p->next=q->next;
free(q);
}
else{
p=p->next;
}
//如果下一个点q->val为val,则p会和q连接,在这种情况下我们并不需要移动p
//因为p->next已经改变了,需要重新判断一下
}
return head;
}
相交链表
1.题目链接
2.题目描述
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null
3.题目分析
- 这个我也看的题解捏(菜菜,哭哭)
- 就还是要设置双指针,而我们的目的就是要让双指针共同走到交汇的结点处,也就是说他俩走过的距离要相等
- 因为两条链表的长度不一定相等,但自交点之后的链表长度就相同了,也就是说需要双指针分别跑完自己的链表 + 对方链表 在相交结点之前 的结点
4.代码实现
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
if(headA==NULL||headB==NULL){ //如果一方为空,则直接返回
return NULL;
}
struct ListNode * n1=headA,*n2=headB;
while(n1!=n2){
n1=(n1==NULL?headB:n1->next); //当遍历完成后,就赋值为对方的头结点
n2=(n2==NULL?headA:n2->next);
}
return n1;
}
复制带随机指针的链表
1.题目链接
138. 复制带随机指针的链表 - 力扣(LeetCode)
2.题目描述
3.题目分析
- 这个题应该是本篇最复杂的题了,如果盲目处理,很容易出现问题,因为新链表不能指向原链表的结点,还需要处理随机指针
- 还是看的题解哈~
- 在每个原结点之后插入和原节点数值相同的新结点,在遍历的同时,访问到原结点的random时,就将新结点的random与原结点的random->next连接;如果random为空,新结点也设为空
- 处理完随机指针后,就需要将所有的新结点连接成一条链表,并且恢复原链表的连接
4.代码实现
struct Node* copyRandomList(struct Node* head) {
if (head == NULL) {
return NULL;
}
struct Node* cur=head;
while(cur!=NULL){ //将每个结点拷贝一份连接在后面
struct Node* node=(struct Node*)malloc(sizeof(struct Node));
node->val=cur->val;
struct Node* next=cur->next;
cur->next=node;
node->next=next;
cur=next;
}
//将随机指针赋给新开辟的结点
struct Node* newhead=NULL,*newtail=NULL,*next=NULL,*newnode=NULL;
cur=head;
while(cur!=NULL){ //原链表的结点
newnode=cur->next; //新链表的结点
next=newnode->next; //原链表的下一个结点
if(cur->random==NULL){
newnode->random=NULL;
}else{
newnode->random=(cur->random)->next;
}
cur=next;
}
//恢复原链表的连接
cur=head; //原链表的结点
while(cur!=NULL){
newnode=cur->next; //新链表的结点
next=newnode->next; //原链表的下一个结点
if(newhead==NULL){
newhead=newtail=newnode; //如果还没有初始化,就直接赋值
}else{
newtail->next=newnode; //尾插
newtail=newnode;
}
cur->next=next; //跳过新结点
cur=next;
}
newtail->next=NULL;
return newhead;
}