【数据结构与算法】LeetCode单链表习题(一)

反转链表

题目链接点这里

在这里插入图片描述

三指针:

解题思路:

  1. 先分析特殊情况,当链表为空或者只有一个结点时,直接返回head即可

  2. 当链表的结点多于一个时,我们首先考虑到是否可以将指针的指向依次反过来,从而实现链表的反转,如下图:

    在这里插入图片描述

    那么要将指针的指向反转过来,可以先定义两个指针n1和n2

    在这里插入图片描述

    n2->next=n1;
    

    但这里有个问题,当n2指向n1时,第二个结点的地址丢失了,因此,还要多定义一个指针n3用于保存第二个结点的地址

    在这里插入图片描述

  3. 当实现第一个结点指向NULL后,n1,n2,n3均向后移动一个结点,使第二个结点指向第一个结点

    在这里插入图片描述

    这里三个指针的移动使用迭代完成,总共分为三步:

    1. n1=n2
    2. n2=n3
    3. n3=n3->next
  4. 重复第三步,直至n2指向NULL时,循环结束,反转链表完成

    这里要特别注意的是,由于是现在进行反转,在迭代,因此当n3指向NULL时,还要进行一次迭代,循环才会结束

    在这里插入图片描述

    在这里插入图片描述

    完整代码
    	struct ListNode* reverseList(struct ListNode* head){
          
          
        if(head==NULL||head->next==NULL) 
            return head;
    
        struct ListNode* n1=NULL,*n2=head,*n3=head->next;
        while(n2)
        {
          
          
            n2->next=n1;
            n1=n2;
            n2=n3;
            if(n3)
                n3=n3->next;
    
        }
        return n1;
    }
    

头插法:

解题思路:

  1. 除了从原来的链表上实现反转,还可以新开辟一个链表,指针newhead指向表头,利用头插法,依次将原来链表的结点头插到新的链表中

    struct ListNode* newhead = NULL;
    
  2. 先定义两个指针cur和next,cur用于从旧链表中取结点头插在新链表中,next同于保存下一个结点,以防cur将结点移动到新链表后,无法重新找到旧链表

  3. 当cur==NULL时,链表完成反转,具体过程如下:

    在这里插入图片描述

    完整代码
    struct ListNode* reverseList(struct ListNode* head){
          
          
        struct ListNode* newhead = NULL;       //开辟新链表
        struct ListNode* cur = head;           //cur用于从旧链表中取结点头插在新链表中
    
        while(cur)			//cur不为NULL时,循环继续
        {
          
          
            struct ListNode* next=cur->next;   //用于保存cur移动结点的下一个结点
    
            cur->next=newhead;					//头插到新链表中
            newhead=cur;						//newNode指向新链表中新的头结点
    
            cur=next;                           //cur回到旧链表中,继续移动下一个结点
        }
        return newhead;
    }
    

链表的中间节点

题目链接点这里

在这里插入图片描述

​ 解法:快慢指针

  1. 根据题目的意思,可知遍历链表后,需返回中间结点
  2. 那么是否可以定义两个指针,当快的指针遍历结束后,慢的指针恰好指向链表的中间结点

在这里插入图片描述

  1. 不难发现,只要快的指针每次比慢的指针多走一步,结束时,慢的指针就能指向链表的中间结点
完整代码
struct ListNode* slow=head;
struct ListNode* fast=head;  //快慢指针起始位置均指向头结点

//当fast==NULL(结点个数为奇数),fast->next==NULL(结点个数为偶数),循环结束,找到中间结点
while(fast&&fast->next)  
{
    
    
    slow = slow->next;		
    fast = fast->next->next;  //慢指针走一步,快指针走两步
}
return slow;

链表中倒数第k个节点

​ 题目: 输入一个链表,输出该链表中倒数第k哥结点。为了符合大多数人的习惯,本题从1开始计数,即 链表的尾结点是倒数第1个结点。例如一个链表有6个结点,从头结点开始它们的值依次是1,2,3,4, 5,6。这个链表的倒数第3个结点是值为4的结点。

解题思路:

  1. 这题和上一题一样,也是要求返回链表中的某个结点,因此,同样可以用快慢指针的方法来解决
  2. 定义快慢指针,但这里稍微不同的是快指针起始位置指向NULL,慢指针指向头结点,快指针提前走k步,然后快慢指针再同时向前移动,那么快指针将一直领先慢指针k个节点,在快指针遍历链表后,慢指针指向的结点就是链表中倒数第k个结点。
struct ListNode*fast=NULL; 	//快指针指向NULL
struct ListNode*slow=pHead;  //慢指针指向头结点
while(k--)		//快指针先走k步
{
    
    
    if(fast)
     	fast=fast->next;
    else
        return NULL;
}
while(fast)   //快慢指针同时移动
{
    
    
    fast=fast->next;
    slow=slow->next;
}
return slow;

合并两个有序链表

题目链接点这里

在这里插入图片描述

  1. 首先考虑特殊情况,若链表都为空则返回其中一个链表即可,若其中一个为空,则返回另一个不为空的链表

    	if(list1==NULL)
        {
          
          
            return list2;
        }
        if(list2==NULL)
        {
          
          
            return list1;
        }
    
  2. 除了第一种特殊情况外,其他情况可以malloc一个带哨兵位的头结点,依次比较list1和list2的结点大小,小的结点优先尾插到新链表,要进行尾插,则要定义一个指向新链表尾结点的指针,记录尾结点的地址

    	Node*head=NULL,*tail=NULL;
    	head=tail=(Node*)malloc(sizeof(Node));
    
    	//取小的尾插
        while(list1&&list2)
        {
          
          
            if(list1->val<list2->val)
            {
          
          
                tail->next=list1;
                list1=list1->next;
            }
            else
            {
          
          
                tail->next=list2;
                list2=list2->next;
            }
            tail=tail->next;
        }
    
  3. 何时结束?当其中一个链表为空时,将另一个链表的剩余部分整体尾插到新链表之后即可

    	if(list1)
            tail->next=list1;
        else
            tail->next=list2;
    

整体流程如下:

在这里插入图片描述

完整代码
typedef struct ListNode Node;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
    
    
    if(list1==NULL) //特殊情况判断
    {
    
    
        return list2;
    }
    if(list2==NULL)
    {
    
    
        return list1;
    }
    Node*head=NULL,*tail=NULL;
    //带哨兵位的头结点,不储存有效数据
    head=tail=(Node*)malloc(sizeof(Node));
    //取小的尾插
    while(list1&&list2)  //两个链表均不为空
    {
    
    
        if(list1->val<list2->val)
        {
    
    
            tail->next=list1;
            list1=list1->next;
        }
        else
        {
    
    
            tail->next=list2;
            list2=list2->next;
        }
        tail=tail->next;
    }
    if(list1)
        tail->next=list1;
    else
        tail->next=list2;
    
    Node *realHead;		
    realHead=head->next;	//真正的头结点是哨兵位的下一个结点
    return realHead;
}

链表分割

题目链接点这里

在这里插入图片描述

思路:

由于在原来链表的基础上无法对链表进行重新排序,那么既然题目要求将比x大的移到x结点之后,比x小的移到x结点之前,我们可以开辟两个链表,一个用来储存比x小的结点(smallhead),一个用来储存比x大的结点(bighead),然后将两个链表连接起来即可。

  1. 遍历原链表,比x小的结点尾插到smallhead,比x大的头插到bighead,最终得到如下两个链表。

在这里插入图片描述

  1. 这里需要注意,由于开辟的链表的头结点是哨兵结点,不储存数据,两个链表链接时,应该是大链表的哨兵位头结点的后一个结点与小链表尾结点链接,大链表尾结点再置空,这样就得到了分隔后的链表。
smalltail->next=bigHead->next;
bigtail->next=NULL;

在这里插入图片描述

完整代码
typedef struct ListNode Node; 
struct ListNode* partition(struct ListNode* head, int x){
    
    
Node *smallHead,*smalltail,*bigHead,*bigtail;
smallHead=smalltail=(Node*)malloc(sizeof(Node));
bigHead=bigtail=(Node*)malloc(sizeof(Node));
smallHead->next=smalltail->next=NULL;
bigHead->next=bigtail->next=NULL;


while(head)
{
    
    
    if(head->val<x)
    {
    
    
        smalltail->next=head;
        smalltail=smalltail->next;
    }
    else 
    {
    
    
        bigtail->next=head;
        bigtail=bigtail->next;
    }
    head=head->next;
}
    smalltail->next=bigHead->next;
    bigtail->next=NULL;
    return smallHead->next;
}

回文链表

题目链接点这里

在这里插入图片描述

思路:

  1. 判断链表是否为回文链表,我们可以从中间结点处断开,再逐一对比每个结点,而如何找到中间结点,依然可以用快慢指针的方法,慢指针每走一步,快指针就走两步,当快指针走到尾时,慢指针指向的就是中间结点,但我们不是要在中间结点后断开,而是要在中间结点的前一个结点断开,那么我们还需要一个指针来保留慢指针的前一个结点。

    在这里插入图片描述

    在这里插入图片描述

  2. 但由于链表不能逆向遍历,因此还要对第二个链表进行反转,反转的方法和上面链表反转题目的第二种方法一样,这里就不赘述了。

完整代码
typedef struct ListNode Node;
bool isPalindrome(struct ListNode* head){
    
    
   Node *fast = head;
   Node *slow = head;
   Node *prev = NULL;
   if(head->next == NULL)
        return true;

   while(fast && fast->next)
   {
    
    
       prev = slow;   //  保留slow的前一个结点
       slow = slow->next;   //快指针走两步
       fast = fast->next->next; //快指针走两步
   }
   prev->next = NULL;//从prev结点后断开

   //链表反转
   Node *newhead = NULL;
   Node *cur = slow;
   while(cur)
   {
    
    
       Node *next =cur->next;

       cur->next = newhead;
       newhead = cur;
       cur = next;
   }

   //两个链表进行比较
   while(head)
   {
    
    
       if(head->val == newhead->val)
       {
    
    
            head = head->next;
            newhead = newhead->next;
       }
       else
            return false;    
   }
   return true;
}
e *newhead = NULL;
   Node *cur = slow;
   while(cur)
   {
    
    
       Node *next =cur->next;

       cur->next = newhead;
       newhead = cur;
       cur = next;
   }

   //两个链表进行比较
   while(head)
   {
    
    
       if(head->val == newhead->val)
       {
    
    
            head = head->next;
            newhead = newhead->next;
       }
       else
            return false;    
   }
   return true;
}

おすすめ

転載: blog.csdn.net/watermelon_c/article/details/122224641