LeetCodeHot100算法题_C语言_链表部分

LeetCode热门100算法题链表部分思路详解

1 相交链表题

对应leetcode160
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

题目描述如下:

示例1:

在这里插入图片描述

示例2-3:

在这里插入图片描述

思路分析:这个题目的关键在于定长,也就是说,两个链表进行尾部对齐,之后较长的那一个链表遍历到较短链表对应头结点的位置,该位置记为起始遍历点,这样确保此时长链表和短链表对应的长度一致,因为相交部分只能在起始遍历点之后,因为在起始遍历点之前,只有长链表有结点,短链表没有结点。
好,总体步骤如下:
1.先"等长" 2.在通过while循环进行遍历 ,根据长短链表对应指针指向位置是否一致进行判断
代码如下:

//Leetcode默认给我们定义了链表的结构体,如下
/*struct ListNode
{
  int val;//结点值
  struct ListNode* next;//结点指向的下一个结点
};
*/
int get_list_length(struct ListNode *head);//获得当前传入链表的长度
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    int long_t = get_list_length(headA);//获得链表A的长度
    int short_t = get_list_length(headB);//获得链表B的长度
    int error_t = 0;//定义步数,即长的链表需要走几步可以和短的链表长度一致

    if(long_t >= short_t)//判断A是不是长链表,是的话让HeadA赋值到起始结点处
    {
        error_t = long_t-short_t;
        while (error_t--)
        {
            headA =headA->next;
        }
    }
    else //B是长链表,让B遍历到起始结点处
    {
        error_t = short_t - long_t;
        while (error_t--)
        {
            headB = headB->next;
        }
    }
    while (headA != NULL)//这写headB!=NULL也一样                          
    {
       if(headA == headB)//判断是否是相交结点是的话直接返回
       {
         return headA;
       }
       headA = headA->next;//指向下一个结点
       headB = headB->next;//指向下一个结点
    }
    return NULL;//循环执行完退出说明没有相交结点,返回NULL即可
}
int get_list_length(struct ListNode *head)//返回链表中的节点数
{
    int len_t = 0;
    //while循环退出是head!=NULL,思考一下head->next !=NULL和head !=NULL区别
    while(head!=NULL)
    {
        len_t++;
        head = head->next;
    }
    return len_t;
}

上面有一个思考在while遍历中head!=NULL,思考一下head->next !=NULL和head !=NULL区别?
其实head!=NULL,退出循环时head是为NULL的此时它不指向任何结点,它的循环次数等于链表中结点的个数,一般用来判断链表长度或者打印链表每一个的节点值
而head->next!=NULL,表明它退出循环时指向的是链表的最后一个结点,该条件常用在尾插法即向链表末尾插入一个结点。第一道题就到这里.

2 相交链表题

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

示例1

在这里插入图片描述

示例2-3:

在这里插入图片描述
思路分析:将链表进行翻转,一共有三种思路,第一种是两两前后翻转,需要定义前后指针以及当前指针,前指针指向始终是当前指针所指结点的前一个结点,后指针的指向始终是当前指针指向的下一个结点,在while循环中进行依次遍历退出条件即为当前指针指向为空,最终返回前指针的指向,即指向的是最后一个结点(也是遍历完成后的头结点)。代码如下:

struct ListNode* reverseList(struct ListNode* head) {
   struct ListNode* pre,*next,*cur;
   cur = head;
   pre = NULL;
   while(cur)
   {
     next = cur->next;//指向当前结点的下一个结点
     cur->next = pre;//当前结点的下一个指向为上一个结点,
     pre = cur;//上一个结点后移
     cur = next;//当前结点后移
   }
   return pre;//返回最后一个节点
}

思路二,新创建一个链表,采用头插法的方式进行翻转,头插法类似于堆栈先入后出的顺序,最先插入的结点最终是链表的末尾结点,最后插入的结点是链表的头结点,根据这个特性实现链表翻转,返回的是新创建的链表。

 void insert_head_node(struct ListNode **head,int data)//头插法插入链表,这里采用二级指针才能改变指针的指向
 																						//因为头插法头结点的值在不断地改变,因此需要二级指针介入
{
    struct ListNode* node = (struct ListNode*)malloc(sizeof(struct ListNode));
    node->val = data;
    node->next = NULL;    
    if(*head != NULL)
        node->next = *head;
    *head = node;
}
struct ListNode* reverseList(struct ListNode* head) {
      if(head == NULL)
    {
        return NULL;
    }
    struct ListNode* temp = NULL;
    while (head!=NULL)
    {
        insert_head_node(&temp,head->val);//向新链表中逆序插入原链表的值实现翻转
        head = head->next;
    }
    return temp;
}

思路三:这个思路跟K个一组翻转链表中的思路一样,只不过K个一组的思路它是多重循环,而这个单重循环,也是定义前后以及当前指针,当前指针永远指向原始的头结点,前指针永远指向第一个结点,每次遍历将当前指针指向的下一个结点放到最前面,当前指针指向结点的下一个结点为下一个结点的下一个结点(这有点绕,看看代码就很容易理解了),遍历次数为当前链表结点数减1。代码如下

struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode* pre,*next,*cur,*temp;
    cur = pre = head;
    temp = head;
    int len = 0;
    if(!head)
       return NULL;
    while(temp)//记录链表结点数
    {
        len++;
        temp = temp->next;
    }
    len = len -1;
    while(len--)
    {
        next = cur->next;//下一个结点
        cur->next = next->next;//当前结点指向的下一个结点为下一个结点指向的下一个结点
        next->next = pre;//下一个结点前移到第一个结点的前面
        pre = next;//第一个结点更新为下一个结点
    }
    return pre; //返回头结点
}

3 回文链表

给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。回文链表链接
在这里插入图片描述
思路:这里的思路有两个,第一个就是定义一个比较大的数组,将链表中的数据放到数组中去,同时记录链表长度,之后我们处理数组,通过从开头以及数组末尾进行挨个比较,退出条件为首指针小于末尾指针(此处的指针指的是索引号)。代码如下

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
bool isPalindrome(struct ListNode* head) {
    if(head == NULL)
       return false;
    int arr[100000];
    int len = 0;
    while(head)//将链表中的结点值全部赋值给数组
    {
        arr[len++] = head->val;
        head = head->next;
    }
    for(int i =0,j = len-1;i < j;i++,j--)//前后一起遍历对应位置值相等为回文链表
    {
        if(arr[i] != arr[j])
          return false;
    }
    return true;
}

思路二:
类似于翻转链表中的返回一个新链表的思想,重新构造一个链表,里面的结点内容与原链表的内容呈现逆序排序的关系,之后一一对比两条链表对应的结点即可,如果相等即为回文链表。

 typedef struct ListNode ListNode;
 void insert_head_node(ListNode **head,int data);
bool isPalindrome(struct ListNode* head) {
    if(head == NULL)
       return false;
    ListNode* temp1 = head;
    ListNode* temp2 = NULL;
    while (temp1)//产生一个新的链表
    {
        insert_head_node(&temp2,temp1->val);
        temp1 = temp1->next;
    }
    while (temp2)//两条链表进行对比,对应位置数据相等即为回文链表
    {
        if(temp2->val != head->val)
           return false;
        temp2 = temp2->next;
        head = head->next;
    }
    return true;
}
void insert_head_node(ListNode **head,int data)//头插法插入数据
{
    ListNode* node = (ListNode*)malloc(sizeof(ListNode));
    node->val = data;
    node->next = NULL;    
    if(*head != NULL)
        node->next = *head;
    *head = node;
}

4 环形链表连接

给你一个链表的头节点 head ,判断链表中是否有环。如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。环形链表链接如下
在这里插入图片描述
在这里插入图片描述
思路:这个思路比较简单,就是单纯的追击问题,即定义快慢指针,快指针每次比慢指针多遍历一次,快指针每次遍历两次,慢指针每次遍历一次。如果是环形链表的话最终会一直在环里面进行转圈,因此肯定在某个时刻,快指针追上慢指针。主要注意一下下一个指针是否为空就可以

bool hasCycle(struct ListNode *head) { //环形链表的判断
   struct ListNode *fast,*slow;
   if(!head || !head->next)//空链表或者具有单一结点的链表全部返回fasle
        return false;
    slow = head;
    fast = head->next;
    while(fast)//只要不为空说明肯定是环形链表
    {
        if(fast == slow)//如果遇到两个节点相等,说明是环形链表
            return true;
        if(!fast->next)
            return false;
        fast = fast->next->next;
        slow = slow->next;
    }
    return false;
}

5 环形链表2

题目要求:给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。不允许修改 链表。环形链表链接
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
思路:其实就是一个数学公式的推导上图是力扣官方的题解,这道题挺有意思。核心思路就是定义两个快慢指针,且从同一位置出发进行遍历,快指针一次走两步,慢指针一次走一步,则快指针走的路径始终是慢指针的两倍,基于此得到上图公式,之后又能简化得到如下的公式

a = c + (n-1)*(b+c) ---> a = c

可以把(b+c)省略,因为指针指了一圈以后发现又回到了原地,因此最终的核心就是从头结点遍历到环形链表相交结点和从快慢指针相交点到环形链表相交点走过的结点数是一样的。为此可以求出快慢指针的相交结点之后,分别从头结点和快慢指针相交结点开始同时遍历直到两者相交。代码如下:

struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode *temp,*fast,*slow;
    if(head ==NULL || head->next ==NULL)
    {
        return NULL;    
    }
    slow = fast = head;
    while(fast)
    {
        if(fast->next)//防止因为fast->next为空导致fast->next->next引用为空发生段错误
            fast = fast->next->next;
        else
            return NULL;
        slow = slow->next;
        if(fast == slow)//找到快慢指针相交节点
        {
            temp = head;
            while(temp)//遍历环形链表相交节点
            {
                if(temp == slow)//找到环形链表相交结点后退出
                    return slow;
                temp = temp->next;
                slow = slow->next;
            }
        }
    }
    return NULL;
}

6 合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
题目链接如下:合并两个有序链表
在这里插入图片描述
思路如下:这道题不难。先将链表中的值放大一个大数组中,之后进行排序,使用冒泡排序的方法进行排序
在创建一个新的链表将排序好的值放回大数组,数组中排序顺序为正序,链表插入为尾插。代码如下:

int get_len(struct ListNode* list);
 struct ListNode* insert(struct ListNode* head,int data);
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
   int t,m;
   m = 0;
   t = get_len(list1)+get_len(list2);
  if(t == 0)
     return NULL;
   int arr[t];
   while(list1)
   {
     arr[m++] = list1->val;
     list1 = list1->next;
   }
   while(list2)
   {
     arr[m++] = list2->val;
     list2 = list2->next;
   }
   int i,j;
   for( i =0;i < t;i++)//冒泡排序。每次把最大的放到后面去
   {
      for(j = 0;j < t-i-1;j++)
      {
         if(arr[j] >arr[j+1])
        {
            int k;
            k = arr[j+1];
            arr[j+1] = arr[j];
            arr[j] = k; 
        }
      }
   }
   struct ListNode* head = NULL;
   for(int l = 0; l < t;l++)
   {
        head = insert(head, arr[l]);
   }
   return head;
}
int get_len(struct ListNode* list)//获得链表的长度
{
    int m_len= 0;
    while(list)
    {
        m_len++;
        list = list->next;
    }
    return m_len;
}
struct ListNode* insert(struct ListNode* head,int data)//尾插法插入链表结点
{
    struct ListNode* temp = (struct ListNode*)malloc(sizeof(struct ListNode));
    temp->val =data;
    temp->next =NULL;
    if(head == NULL)
    {
       head = temp;
       return head;
    }
    struct ListNode* temp1 = head; 
    while(temp1->next)
    {
        temp1 = temp1->next;
    }
    temp1->next = temp;
    return head;
}

7 两数相加

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。请你将两个数相加,并以相同形式返回一个表示和的链表。你可以假设除了数字 0 之外,这两个数都不会以 0 开头。力扣链接如下
在这里插入图片描述
在这里插入图片描述
这道题思路如下:
这道题主要注意三个点:两个链表长短不一,也就是在使用循环条件时注意是长度链表完全遍历完后才退出循环,短的遍历完后它结点就没了,此时长的结点遍历时短的链表结点就不存在,则它的链表值一律按照0进行处理。第二个就是加的时候根据小学学过的加法运算这个需要你加上进位标志也就是上一个数的进位标志。第三个就是while循环退出后会多分配一个结点内存,这个主要是判断最后一个数是否有进位标志有的话就加上1没得话就为0

struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2) {
    struct ListNode* temp = (struct ListNode*)malloc(sizeof(struct ListNode));
                                     //定义一个新链表,存储的是两个链表相加///后的值
    struct ListNode* pre,*temp2;//pre链表用于指向倒数第二个结点,根据进位标值来决定最后一个结点需不需要
    //如果进位标志为1,也就是说倒数第二个值即两个链表相加的结果超过10需要进位则需要最后一个结点否则不需要
    temp2 = temp;//记录新的返回链表的头结点
    int sum,jinwei_flag,l1_data,l2_data;
    jinwei_flag = 0;//进位标志位
    while(l1 !=NULL || l2 !=NULL)//但两个链表全为空的时候进行退出,这个要注意
    {
        l1_data = (l1!=NULL)? l1->val:0;//查看当前对应结点是否存在,不存在的话链表值一律按照0进行处理
        l2_data = (l2!=NULL)? l2->val:0;
        sum = l1_data+l2_data+jinwei_flag;//新链表每一个结点的值等于两个结点值相加加上上一个结点的进位值
        jinwei_flag = sum/10;//判断当前结点相加的值是否超过10,超过的话进位值置1否则置0
        sum = sum%10;//当前结点的值进行取余,目的是如果有进位只取个位就行,就是小学加法规则
        temp->val = sum;//赋给当前结点
        temp->next = (struct ListNode*)malloc(sizeof(struct ListNode));//创建一个新的结点
        pre = temp;
        temp = temp->next;
        if(l1)//链表不为空继续遍历下一个结点
            l1 = l1->next;
        if(l2)
            l2 = l2->next;
    }
    if(jinwei_flag)//判断最后一个结点值进位值是否为1,为1的说明有进位,则最后一个结点需要赋1同时它的下一个指向为空
    {
        temp->val = 1;
        temp->next = NULL;
    }
    else//进位为零即最后一个结点不需要,选择最后一个结点的上一个结点作为末尾结点即可,同时将该结点进行删除。
    {
        pre->next = NULL;
        free(temp);
    }
    return temp2;
}

8 删除链表倒数第N个结点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。链接如下:
力扣链接如下
在这里插入图片描述
思路:这个转换一下,假设链表M个结点,删除倒数第N个结点,即删除正数第M-N+1个结点,但是要删除第M-N+1个结点需要知道整数第M-N个结点因为它的下一个结点指向的是要删除的结点,遍历到第M-N个结点需要遍历M-N-1个步长,这个一定要了解,后续代码是根据这个进行更改的。代码如下:

struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
   int k = 0;
   struct ListNode* temp,*temp1,*temp2;
   temp = temp1 = temp2 = head;
   while(temp)//遍历链表长度
   {
     k++;
     temp = temp->next;
   }
   if(k-n == 0)//如果删除的是头结点直接返回头结点的下一个
   {
     return head->next;
   }
   for(int i = 0; i<k-n-1;i++)
   {
        temp1 = temp1->next;
   }
   temp2 = temp1->next;//指向被删除的结点
   if(temp2)//如果删除的是末尾结点需要做判断防止引用空指针
        temp1->next = temp2->next;
    else
        temp1->next = temp2;
   return head;
}

9 两两交换链表中的节点

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。力扣链接如下
在这里插入图片描述
思路:主要就是递归实现,第一个结点的指向是原先第二个结点的下一个结点(此处会调用递归,递归返回的是传入链表的头结点,这里是第三个链表结点地址,之后会一直递归调用,退出条件是第一个结点为空或者第二个结点为空),第二个结点的指向是第一个结点注意链表的位置关系即可,代码如下:

struct ListNode* swapPairs(struct ListNode* head) {
  if(head == NULL || head->next == NULL)
     return head;//递归的退出条件头结点为空或者第二个结点为空
  struct ListNode* temp = head->next;//交换前的第二个结点
  head->next = swapPairs(temp->next);//交换前的第一个结点指向原先第二个结点的下一个经过交换后的头结点
  temp->next = head;//交换前的第二个结点指向原先的第一个结点
  return temp;//返回交换后的头结点
}

10 K个一组翻转链表

给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。链接如下
在这里插入图片描述
在这里插入图片描述
思路:这道题跟第二道题的翻转链表思路三是一样的,只是多了一个外循环,定义一个哨兵结点目的是
记录最终返回链表的头结点,对循环几次需要做出一个判断,比如一共5个值,每三个为一组进行翻转,则前三个翻转,后两个不翻转。翻转的思路是使用指针记录每组开头的第一个结点的位置,之后把该结点的下一个结点放到每组的开头,这样以来只需要每组结点数减1就能翻转一个组。代码如下:

struct ListNode* reverseKGroup(struct ListNode* head, int k) {
   struct ListNode* temp = head;
   struct ListNode *guard = (struct ListNode*)malloc(sizeof(struct ListNode));//定义一个哨兵结点
   struct ListNode* cur,*pre,*next;
   cur = guard->next = head;
   pre = guard;
   int len = 0;
   while(temp)//记录链表长度
   {
     len++;
     temp = temp->next;
   }
   int count = len/k;
   if(!count)
     return head;
   while(count--)//判断需要翻转几个组
   {   
    for(int i =0; i<k-1;i++)
      {
        next = cur->next;//下一个结点位置为当前结点的下一个指向
        cur->next = next->next;//当前结点的下一个结点为下一个结点的下一个结点指向
        next->next = pre->next;//下一个结点的指向为当前组的第一个结点
        pre->next = next;//哨兵结点指向当前组的第一个结点
      }
      pre = cur;//指向当前组的最后一个结点,充当下一个组的哨兵结点
      cur = cur->next;//指向下一组的第一个结点,且cur在下一个组遍历中执行永远不变
   }
   return guard->next;//返回哨兵结点的指向即第一个翻转组的第一个结点
}

这里为什么需要使用哨兵结点就是必须给他分配一个内存而不是用指针之间指向,其实用指针也行但是感觉有点麻烦这个是使用指针不使用哨兵结点的方法,其实差不多只不过在第一次循环时需要额外处理一下。代码如下:

struct ListNode* reverseKGroup(struct ListNode* head, int k) {
   struct ListNode* temp = head;
   struct ListNode* cur,*pre,*next,*guard;//不给哨兵结点分配内存而是直接定义一个指针
   cur = guard = head;
   pre = head;
   int len = 0;
   while(temp)
   {
    len++;
     temp = temp->next;
   }
   int count = len/k;
   if(!count)
     return head;
     int a =1 ;
   while(count--)
   {
     if(a  ==1)
     {
      for(int i =0; i<k-1;i++)//由于定义的是指针,在第一次循环时不能用pre->next,只能用pre,因此此处for循环只执行一次
      {
        next = cur->next;
        cur->next = next->next;
        next->next = pre;
        pre = next;
      }
        guard = pre;
     }
     else
     {
    for(int i =0; i<k-1;i++)//之后的话同哨兵结点一样了
      {
        next = cur->next;
        cur->next = next->next;
        next->next = pre->next;
        pre->next = next;
      }
     }
      a--;
      pre = cur;
      cur = cur->next;//指向下一组的第一个结点
      //pre = cur;//指向当前组的最后一个结点
   }
   return guard;
}

11 随机链表复制

给你一个长度为 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.将新链表复制到旧链表的尾部
2.随机链表的赋值 3.拆解原有的链表。
思路图如下:
将每一个原链表的下一个指向全部变成它本身结点的拷贝,同时将拷贝的结点的下一个指向变成原链表的下一个指向。

在这里插入图片描述下面这张图显示了原链表的随机指向和复制链表的随机指向的关系,从中我们不难发现,原先链表1
的随机链表指向链表3,而复制链表1也应该指向链表三,这里面有一个关系式即复制结点的随机指向应该是对应原结点的随机指向的下一个结点,即原结点1随机指向3,而3的下一个指向是复制的3结点,这个结点是复制结点1的随机指向,这里这个关系式非常的巧妙,但同时也是我们写以下代码时用到的基础。
在这里插入图片描述

代码中对应的结构体如下
 * struct Node {
 *     int val;
 *     struct Node *next;
 *     struct Node *random;
 * };

代码如下:

struct Node* copyRandomList(struct Node* head) {
	struct Node *cur,*copy,*next;
    if(head == NULL)
    {
        return NULL;
    }
    //先复制链表
    cur = head;
    while(cur)
    {
        next = cur->next;//下一个结点
        copy = (struct Node*)malloc(sizeof(struct Node));
        copy->random = NULL;//这个值一定为空,因为第二步会对随机指向值进行处理,这个需要设置为NULL
        copy->next = next;
        copy->val = cur->val;
        cur->next = copy;
        cur = next;
    }
    //复制辅助链表
    cur = head;
    while(cur)
    {
        copy = cur->next;//复制链表
        if(cur->random != NULL)
        {
            copy->random = cur->random->next;//核心就是复制结点的随机指向为原结点随机指向的下一个结点
        }
        else
           copy->random = NULL;
        cur = copy->next;
    }
    //恢复原始链表
    cur = head;
   struct Node * return_copy = cur->next;
    while(cur)
    {
        copy = cur->next;//复制链表中的结点
        next = copy->next;//原链表结点中的下一个结点
        cur->next = next;//原链表中的结点指向原链表中的下一个结点
        if(next)
            copy->next = next->next;//复制链表结点指向复制链表结点的下一个结点
        else
            copy->next = NULL;
        cur = next;
    }
    return return_copy;
}

12 排序链表

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。力扣链接如下
在这里插入图片描述
思路:这个题的思路其实比较直接,直接使用排序,但是好像不能用冒泡这类的排序,会报超时错误,因此要学习标准库中qsort的使用。首先就是定义一个数组,将链表中各个结点的值放到数组中去,之后调用qsort函数进行排序,最后从新遍历结点将数组中的值赋值给原链表中的结点就行。
先来看一下qsort函数是如何使用的。
qsort:

void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
参数1:void *base 指向待排序数组的指针(void* 类型),可以是任意数据类型的数组
参数2:size_t nmemb 数组中元素的个数
参数3: size_t size  每个元素的大小(字节数),例如 sizeof(int)
参数4:int (*compar)(const void *, const void *)) 是一个函数指针

对应代码如下:

//qsort 中,比较函数的返回值决定了元素的相对顺序:
//返回正值:表示 a 应该排在 b 之后。
//返回负值:表示 a 应该排在 b 之前。
//返回 0:表示 a 和 b 相等。
int compare(const void* a,const void* b)
{
    if(*((int*)a) < *((int*)b))
        return 1;//这里是降序排序,即a排在b的
    else
        return -1;
}
typedef struct ListNode ListNode;
struct ListNode* sortList(struct ListNode* head) {
   int t_count,t_len;
    t_count = t_len = 0;
    ListNode* temp = head;
    if(!head)
      return head;
    while(temp)//记录链表中结点的个数
    {
        t_len++;
        temp = temp->next;
    }
    int arr[t_len];
    temp = head;
    while(temp)//将链表中结点的值放在数组中
    {
        arr[t_count++] = temp->val;
        temp = temp->next;
    }
    qsort(arr,t_count,sizeof(int),compare);//对数组中的值也是链表中的值进行降序排序
    temp = head;
    while(temp)
    {
        temp->val = arr[--t_count];//由于是从数组末尾开始逆序读取,因此数组中排序为降序排序
        temp = temp->next;
    }
    return head;
}

13 合并 K 个升序链表

给你一个链表数组,每个链表都已经按升序排列。请你将所有链表合并到一个升序链表中,返回合并后的链表。力扣链接
在这里插入图片描述
思路:这个题跟上面那个题其实差不多,就是多了层循环,因为它是由多个链表组成,因此呢我们每次读取一个链表将值赋值给数组中去就可以,同时最后要自己开辟内存空间将数组中的值赋给新的链表即可。
代码如下:

int compare(const void* a,const void* b)
{
    if(*((int*)a) > *((int*)b))
        return 1;//a在b的后面返回为1,故为升序排序
    else
        return -1;
}
struct ListNode* mergeKLists(struct ListNode** lists, int listsSize) {
    int array[10000];//题目中给的数据个数在0到10000之内因此直接定义最大值就可以
    int len = 0;
    int count = 0;
    struct ListNode* temp;
    while(len < listsSize)//将链表中的所有数据存到数组中
    {
        temp = lists[len++];
        while(temp)
        {
            array[count++] = temp->val;
            temp = temp->next;
        }
    }
    qsort(array,count,sizeof(int),compare);
    struct ListNode* cur = NULL;
    while(count--)
    {
        temp = (struct ListNode* )malloc(sizeof(struct ListNode));//逆序法插入结点即头插法,由于是顺序输出,所以逆序插入时大的值先插,所以数组排序对应为升序。
        temp->val = array[count];
        temp->next = cur;
        cur = temp;
    }
    return cur;
}

14 题待定,留个坑,有时间的话回来再填

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值