面试高频~单链表OJ题总结(一)

1.力扣208:移除链表元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

在这里插入图片描述
分析:
1.考虑链表为空的情况
2.考虑头结点等于val的情况

struct ListNode* removeElements(struct ListNode* head, int val){
    
    
    //1.生成新的链表头(用来防止原链表头需要删除的情况)
    struct ListNode* newhand = malloc(sizeof(struct ListNode));
     newhand->next=head;
     struct ListNode*cur=newhand;
     //2.空链表返回空
     if(head==NULL){
    
    
         return NULL;
     }
     //3.遍历链表
     while(cur->next){
    
    
         //3.1.不等于val cur向后走
         if(cur->next->val!=val){
    
    
             cur=cur->next;
         }//3.2.等于val 让cur的下一个指向cur的下下一个
         else{
    
    
             cur->next=cur->next->next;
         }
     }
    //4.返回链表
    return newhand->next;
}

在这里插入图片描述

2.力扣206:反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

分析:相当于头删接头插,注意先后顺序即可

struct ListNode* reverseList(struct ListNode* head){
    
    
    //空链表返回空
    if(head==NULL){
    
    
        return NULL;
    }
    struct ListNode* cur=head;
    struct ListNode* newhead=NULL;
    //遍历链表
    while(cur){
    
    
         struct ListNode* fist=cur->next;//保存下一个结点
         //头删
         cur->next=newhead;
         //头插
         newhead=cur;
         cur=fist;
    }
    return newhead;
}

在这里插入图片描述

3.力扣867:链表的中间结点

给定一个头结点为 head 的非空单链表,返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

分析:经典的快慢指针问题, 快指针=2倍慢指针
1.奇数个结点,当快指针到最后一个结点时慢指针到中间位置
2.偶数个结点,当快指针到NULL时慢指针到两个中间结点的第二个结点
思考:当为偶数个结点,如何返回两个中间结点的第一个结点
在这里插入图片描述

struct ListNode* middleNode(struct ListNode* head){
    
    
    struct ListNode*prev=head,*cur=head;
    //如果是偶数个结点,返回第二个中间结点
    //如果需要返回第一个中间结点 while(cur->nexy&&cur->next->next)
    while(cur&&cur->next){
    
    
        prev=prev->next;
        cur=cur->next->next;
    }
    
    return prev;
}

4.剑指offer:链表的倒数第k个结点

输入一个链表,输出该链表中倒数第k个结点。

分析:可以让一个指针从头先走k步,再让另一个指针开始从头走,当快指针到NULL时慢指针到达倒数第K个结点。
1.注意k的范围 0<k<总结点个数
2.如果k大于总结点个数,fist在先走的过程中会到达NULL
在这里插入图片描述

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
    
    
    struct ListNode* fist=pListHead;
    struct ListNode* slow=pListHead;
    //k<0或者链表为空时返回NULL;
    if(k<=0||pListHead==NULL) return NULL;
    //让fist先走k个结点
    for(int i=0;i<k;++i){
    
    
       //防止k大于总结点个数,会人fist提前到达NULL;也返回NULL;
        if(fist==NULL){
    
    
            return NULL;
        }
        fist=fist->next;
    }
    //在fist到达NULL之前,快慢指针一起走
    while(fist){
    
    
        fist=fist->next;
        slow=slow->next;
    }
    //fist到达NULL;返回慢指针slow
    return slow;
}

5.力扣21:合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

分析:
1.有一个链表为空可直接返回另一个链表
2.因为要修改头结点,可malloc出一个新结点当头
3.遍历比较两个链表的值,将小的值的结点接到新头后面,知道有一个链表为空,将另一个链表拼接上即可
在这里插入图片描述

struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
    
    
    //给一个新头结点,指向NULL
   struct ListNode* newhead=malloc(sizeof(struct ListNode));
    newhead->next=NULL;
    struct ListNode*cur=newhead;
    //如果一个为空,直接返回另一个链表
    if(l1==NULL) return l2;
    if(l2==NULL) return l1;
    //遍历比较两个链表,直到有一个到NULL
    while(l1&&l2){
    
    
        //将较小的值接到新链中
        if(l1->val>=l2->val){
    
    
            cur->next=l2;
            cur=l2;
            l2=l2->next;
        }
        else{
    
    
            cur->next=l1;
            cur=l1;
            l1=l1->next;
        }
    }
    //有一个链表空了,将另一个链表接到后面
    if(l1==NULL) cur->next=l2;
    if(l2==NULL) cur->next=l1;
    return newhead->next;
}

6.牛客CM11:链表分割

现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。

分析:
1.可以建立两个头,一个用来链接小值,一个用来链接大值
2.保存两个链表的尾,方便尾插
3.将大值链表链接到小值链表之后

class Partition {
    
    
public:
    ListNode* partition(ListNode* pHead, int x) {
    
    
        //两个头1为小值头2为大值头
        struct ListNode* newhead1=(struct ListNode*)malloc(sizeof(struct ListNode));
        struct ListNode* newhead2=(struct ListNode*)malloc(sizeof(struct ListNode));
        struct ListNode* cur=pHead;
        //尾指针,方便尾插和最后的链接
        struct ListNode* tail1=newhead1;
        struct ListNode* tail2=newhead2;
        while(cur){
    
    
            //将小于x的值尾插到小值链表
            if(cur->val<x){
    
    
                tail1->next=cur;
                tail1=tail1->next;
            }
            //大于等于x的值尾插到大值链表
            else{
    
    
                tail2->next=cur;
                tail2=tail2->next;
            }
            cur=cur->next;
        }
        //将大值链表的最后结点指向NULL,并将其大值链表头尾插到小值链表
        tail2->next=NULL;
        tail1->next=newhead2->next;
        return newhead1->next;
    }
};

7.牛客OR36:链表的回文结构

对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。
给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。
测试样例:
1->2->2->1
返回:true

分析:
1.空链表或者链表长度为1一定是回文结构
2.回文结构一定是中心对称的,可以找到链表的中间结点。
3.将中间结点后的链表逆置形成新链表
4.将原链表与逆置的新链表进行比较如果新链表到空即为回文
注意:在进行比较的时候原链表长度>=新链表,所有循环条件为新链表不为空

class PalindromeList {
    
    
public:
    bool chkPalindrome(ListNode* A) {
    
    
        if(A==NULL||A->next==NULL){
    
    
            return true;
        }
        //先找中间结点(slow)
        struct ListNode *fist=A;
        struct ListNode *slow=A;
        while(fist&&fist->next){
    
    
            slow=slow->next;
            fist=fist->next->next;
        }
        //逆置后半段链表
        struct ListNode *newhead=NULL;
        struct ListNode *cur=slow;
        while(cur){
    
    
            struct ListNode* save=cur->next;
            cur->next=newhead;
            newhead=cur;
            cur=save;
        }
        //用原链表与后半段链表比较
        while(newhead){
    
    
            if(A->val!=newhead->val){
    
    
                return false;
            }
            A=A->next;
            newhead=newhead->next;
        }
        return true;
    }
};

Guess you like

Origin blog.csdn.net/m0_58103115/article/details/121572638