链表刷题集合

Posted  落禅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了链表刷题集合相关的知识,希望对你有一定的参考价值。

链表刷题集合

160. 相交链表

给你两个单链表的头节点 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 个节点。

思路:
1.先找出两条链表中较长的哪一个
2.让长链表先走他们的差距部,让其两者头指向同一位置
3.两个一起开始走,当两者相等时就返回该节点,循环完之后没有返回就返回null
特殊情况处理:
1.当两个链表的头指针都为null时,返回努力了
2.当两者头节点有一个为null时也返回null

注意:
链表中找相交结点时一定找的是他们的地址相同处,因为链表的结点都是动态开辟的,第一个相交结点他们的地址
一定相同,而不能只是简单的比较一下值

代码

/**

 * Definition for singly-linked list.

 * struct ListNode 

 * int val;

 * struct ListNode *next;

 * ;
   */
    //思路:1.找到两个链表中较大的那一个,然后让长链表先走他们的差距部,等到他们位于同一起点,然后两者
    //       一起走,直到两者到达同一节点处和
    //特殊情况的处理:
    //1.两个链表的只想都为null,返回true,两个链表中只有一个指向为null,返回false
   struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) 
   //1.两个头指针都为null
   if(headA==NULL&&headB==NULL)
   
       return headA;
   
   //2.两个结点其中一个为空
   if(headA==NULL||headB==NULL)
   
       return NULL;
   

   //3.排除特殊情况之后,我们就要开始处理正常情况
   int LenA=0,LenB=0;
   struct ListNode*curA=headA;
   struct ListNode*curB=headB;
   while(curA!=NULL)
   
       LenA++;
       curA=curA->next;
   
   while(curB!=NULL)
   
        LenB++;
        curB=curB->next;
    
    int n=abs(LenA-LenB);
    if(LenA>LenB)
    
        while(n--)
        
            headA=headA->next;
        
    
    else
    
        while(n--)
        
            headB=headB->next;
        
    
    curA=headA;
    curB=headB;
    while(curA!=NULL&&curB!=NULL)
    
        if(curA==curB)
        
            return curA;
        
        curA=curA->next;
        curB=curB->next;
    
    return NULL;

203. 移除链表元素

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

实例1:
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
实例2:
输入:head = [], val = 1
输出:[]
实例3:
输入:head = [7,7,7,7], val = 7
输出:[]

本题是一道简单题,但是仍然有几个需要注意的点:

1.如果头指针直接为空

2.头指针不为空但只有一个元素且为要删除的元素

3.全部都是要删除的元素

最好的方式就是定义一个哨兵位的头节点,然后向后遍历删除val,这样就避免了头指针为空的情况和头指针不为空且只有一个元素或者要删除的元素是链表的全部结点,这种方式代码如下所示:

**
 * Definition for singly-linked list.
 * struct ListNode 
 *     int val;
 *     struct ListNode *next;
 * ;
 */


struct ListNode* removeElements(struct ListNode* head, int val)
    //创建一个哨兵位的头节点
    struct ListNode*pHead=(struct ListNode*)malloc(sizeof(struct ListNode));
    //连接头节点和这个链表的元素
    pHead->next=head;
    //cur指向现在的头节点
    struct ListNode*cur=pHead;
    //prev作为前驱指针
    struct ListNode*prev=NULL;
    while(cur!=NULL)
    
    //如果cur->val==val,执行删除操作,其中prev指向前驱指针,Next指向要删除元素的下一个地方
        if(cur->val==val)
        
            struct ListNode*Next=cur->next;
            prev->next=Next;
            free(cur);
            cur=Next;
        
        else
        
        此时prev总是比cur少一个
            prev=cur;
            cur=cur->next;
        
    
    return pHead->next;

如果不用这种方式,而是要用普通方法创建,就比较复杂,考虑全上面3中情况,代码如下:

/** * Definition for singly-linked list. 
* struct ListNode  
*     int val;
*     struct ListNode *next; 
* ; 
*/  
struct ListNode* removeElements(struct ListNode* head, int val) 
    //本题特殊情况:当头结点为val时
   struct ListNode*cur=head;
   struct ListNode*prev=NULL;
   while(cur!=NULL)
    
        struct ListNode*next=cur->next;
        if(cur->val==val)
         
         //当第一个节点为val时,代表prev为空,那我们直接释放这个节点,然后让head指针指向下一个元素 
         //然后让将下一个节点赋给cur
             if(prev==NULL)
              
                 head=next;
                 free(cur);
                 cur=next;
              
        //当第一个节点不为空时,我们使prev直接指向cur的下一个元素,释放cur,将next赋给cur
             else
             
                prev->next=next;
                free(cur); 
                cur=next;
             
         
        //如果该节点不是val,那么将cur赋给prev,然后cur指向下一个元素
        else 
        
            prev=cur; 
            cur=cur->next; 
         
     
    return head;

剑指 Offer II 024. 反转链表

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

示例 1:

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

示例 2:

输入:head = [1,2]
输出:[2,1]

示例 3:

输入:head = []
输出:[]

这里我们有两种方法进行书写

方法一:头插法:将每一个节点进行头插,这样实现对链表的逆置

class Solution 
public:
    ListNode* reverseList(ListNode* head) 
        //判断头结点是否为空
        if(head==nullptr)
        return nullptr;
        ListNode*cur=head;
        //创建新的头结点
        ListNode*NewHead=nullptr;
        while(cur)
        
            ListNode*next=cur->next;
            //使当前节点的下一个元素指向新的头结点
            cur->next=NewHead;
            //使得cur成为新的头节点
            NewHead=cur;
            cur=next;
        
        return NewHead;
    
;

148. 排序链表

你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。

进阶:

你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?

示例 1:

输入:head = [4,2,1,3]
输出:[1,2,3,4]

示例 2:

输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]

示例 3:

输入:head = []
输出:[]

链表排序的最佳方法:归并排序

1.找到中间节点

2.将中间节点断成左右两半,然后再次递归找到中间节点,再次进行分割,直到最后分割成一个一个的单个元素

3.然后两两排序,之后四四排序…直到最后将整张链表排成一个有序链表

这个思想和归并排序一样,归并排序的原理也是将数据分割成小部分,然后每一个小部分进行排序,让局部有序,然后整体有序

class Solution 
public:
//链表的排序:归并排序
//1.找到中间节点
//2.将链表分为两部分
//3.实现有序链表的排序
    ListNode*Sort(ListNode*&l,ListNode*&r)
    
    //到这里就是排序的思想了,排序思想和合并两个有序链表的思想差不多
    //创建新的节点,然后按照插入排序的思想,谁大谁就插入
        ListNode*dummyHead=new ListNode(0);
        ListNode*cur=dummyHead;
        while(l!=nullptr&&r!=nullptr)
        
            if(l->val<=r->val)
            
                cur->next=l;
                cur=cur->next;
                l=l->next;
            
            else
            
                cur->next=r;
                cur=cur->next;
                r=r->next;
            
        
        if(l!=nullptr)
        
            cur->next=l;
        
        if(r!=nullptr)
        
            cur->next=r;
        
        return dummyHead->next;
    
    //传递参数时记得引用传递
    ListNode*MergSort(ListNode*&head)
    
    //判断头节点的下一个节点是否为空,如果是,直接返回头节点
        if(head->next==nullptr)
        return head;
        //定义快慢指针,寻找中间节点
        ListNode*slow=head;
        ListNode*fast=head;
        ListNode*prev=nullptr;
        while(fast&&fast->next)
        
            prev=slow;
            slow=slow->next;
            fast=fast->next->next;
        
        //此时slow所在的位置就是中间节点所在的位置,prev指向slow的前一个节点
        //我们人为的让链表从slow这个地方断开,head->prev是链表的前半段,slow到完是链表的右半部分
        prev->next=nullptr;
		//递归在左、右两端继续找中间节点
        ListNode*l=MergSort(head);
        ListNode*r=MergSort(slow);
        //当分割的不能分割时,进行排序,最开始两两排序,到后来四四排序
        return Sort(l,r);
    
    ListNode* sortList(ListNode* head) 
    //1.判断节点是否为空
        if(head==nullptr)
        return head;
        //2.进入归并排序,寻找中间节点
        return MergSort(head);

    
;

725. 分隔链表

给你一个头结点为 head 的单链表和一个整数 k ,请你设计一个算法将链表分隔为 k 个连续的部分。

每部分的长度应该尽可能的相等:任意两部分的长度差距不能超过 1 。这可能会导致有些部分为 null 。

这 k 个部分应该按照在链表中出现的顺序排列,并且排在前面的部分的长度应该大于或等于排在后面的长度。

返回一个由上述 k 部分组成的数组。

输入:head = [1,2,3], k = 5
输出:[[1],[2],[3],[],[]]
解释:
第一个元素 output[0] 为 output[0].val = 1 ,output[0].next = null 。
最后一个元素 output[4] 为 null ,但它作为 ListNode 的字符串表示是 []

输入:head = [1,2,3,4,5,6,7,8,9,10], k = 3
输出:[[1,2,3,4],[5,6,7],[8,9,10]]
解释:
输入被分成了几个连续的部分,并且每部分的长度相差不超过 1 。前面部分的长度大于等于后面部分的长度。

思路:

1.求链表得节点个数

2.如果节点个数<K,说明节点个数不够分割,那么就让前面几个节点单独为一个,后面的都为NULL,凑齐k个

3.链表节点>K并且有剩余,那么我们就k个节点为一组,如果有剩余,那么剩余的节点每次多添加一个给前面的节点

class Solution 
public:
    vector<ListNode*> splitListToParts(ListNode* head, int k) 
        vector<ListNode*>ret;
        //首先遍历得到链表的长度
        //每k个一组进行分割,分别进入链表
        ListNode*cur=head;
        int sum=0;
        while(cur)
        
            sum++;
            cur=cur->next;
        
        int temp=sum/k;//判断每组有多少个节点
        cur=head;
        //temp==0说明链表节点少于K个,那么就将链表单独分割,不足的用nullptr代替
        if(temp==0)
        
            //此时说明k>sum,说明不够分
            while(k--)
            
                if(cur)
                
                    ListNode*next=cur->next;
                    cur->next=nullptr;
                    ret.push_back(cur);
                    cur=next;
                
                else
                
                    ret.push_back(nullptr);
                
            
            return ret;
        
		//让cur重新指向头
        cur=head;
        //next用来记录cur得下一个位置
        ListNode*next=cur->next;
        //phead指向cur的起始位置,以便于后续将该节点加入到数组中
        ListNode*phead=cur;
        //判断有没有余数,如果有余数则一次让前面几个组的temp值+1
        int remind=sum%k;
        //k用来控制外层循环,即用来表示要分的组数
        while(k--)
        
        //里面即表示每组的成员
            int val=temp;
            if(remind!=0)
            val+=1;
            //从cur位置开始往后走val步即到达每个组的数量
            while(--val)
            
                cur=cur->next;
            
            next=cur->next;
            if(cur!=nullptr)
            cur->next=nullptr;
            //入数组
            ret.push_back(phead);
            //重新使cur指向下一个位置
            cur=next;
            //phead重新指向cur的起始位置
            phead=cur;
            //余数--
            if(remind)
            remind--;
        

        return ret;


    
;

430. 扁平化多级双向链表

多级双向链表中,除了指向下一个节点和前一个节点指针之外,它还有一个子链表指针,可能指向单独的双向链表。这些子列表也可能会有一个或多个自己的子项,依此类推,生成多级数据结构,如下面的示例所示。

给你位于列表第一级的头节点,请你扁平化列表,使所有结点出现在单级双链表中。

输入:head = [1,2,3,4,5,6,null,null,null,7,8,9,10,null,null,11,12]
输出:[1,2,3,7,8,11,12,9,10,4,5,6]
解释:

输入的多级列表如下图所示:

扁平化后的链表如下图:

示例 2:

输入:head = [1,2,null,3]
输出:[1,3,2]
解释:

输入的多级列表如下图所示:

1—2—NULL
|
3—NULL
示例 3:

输入:head = []
输出:[]

思路:将该图旋转一下,发现它非常像一颗二叉树,child相当于左孩子,next相当于右孩子,那么我们只要前序遍历这颗二叉树,就能得到正确答案

/*
// Definition for a Node.
class Node 
public:
    int val;
    Node* prev;
    Node* next;
    Node* child;
;
*/

class Solution 
public:
//创建全局变量,设置一个哨兵位置的头节点
      Node*dummy=new Node();
        Node*p=dummy;
    void dfs(Node*&head)
    
    //将head连接到p->next
        p->next=new Node();
        p->next->val=head->val;
        p->next->prev=p;
        p=p->next;
        //递归遍历左子树
        if(head->child)
        
            dfs(head->child);
        
//递归遍历右子树
        if(head->next)
        
            dfs(head->next);
        
        
    
    
    Node* flatten(Node* head) 
    //判断头节点是否为空
        if(head==nullptr)
        return nullptr;

        dfs(head);
        //将最后一个节点的下一个位置置为nullptr
        p->next=nullptr;
        //将第一个节点与前面哨兵位置断开
       dummy->next->prev=nullptr;

        return dummy->next;
        
    
;

删除有序链表中重复的元素-I

删除给出链表中的重复元素(链表中元素从小到大有序),使链表中的所有元素都只出现一次
例如:
给出的链表为1->1->-2,返回1->2.
给出的链表为1->1->2->3->3,返回1→2→3.

数据范围:链表长度满足 0 \\le n \\le 1000≤n≤100,链表中任意节点的值满足 |val| \\le 100∣val∣≤100
进阶:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)
输入:
1,1,2
复制
返回值:
1,2

​ 代码如下所示

/**
 * struct ListNode 
 *	int val;
 *	struct ListNode *next;
 * ;
 */

class Solution 
public:
    /**
     * 
     * @param head ListNode类 
     * @return ListNode类
     */
    ListNode* deleteDuplicates(ListNode* head) 
        // write code here
        if(head==nullptr||head->next==nullptr)
        
            return head;
        
        ListNode*cur=head;
        while(cur&&cur->next)
        
            ListNode*next=cur->next;
            //前后双指针进行判断
            if(cur->val==next->val)
            
                cur->next=next->next;
                delete next;以上是关于链表刷题集合的主要内容,如果未能解决你的问题,请参考以下文章

链表刷题集合

链表刷题总结

leetcode之哈希表刷题总结1

刷题记录-剑指offer36:二叉搜索树与双向链表

刷题记录-剑指offer36:二叉搜索树与双向链表

数据结构刷题集合—— 链表