“手撕“9道经典链表算法题

目录

前言:

经典题目:

1,移除链表元素 - 力扣(LeetCode)

    思路一(修改原链表): 遍历链表,将每个数值等于val的节点删除。

    思路二(建立新链表):建立一个新链表,插入每个数值不等于val的节点。

代码实现

部分细节补充:

2, 反转链表 - 力扣(LeetCode)

    分析:

    思路(三指针法):

部分细节补充:

3, 合并两个有序链表 - 力扣(LeetCode)

分析:

     思路一(修改原链表):

      思路二 (建立新链表):

代码实现:

4,链表的中间结点 - 力扣(LeetCode)

分析:

   思路一(遍历算长度,分奇数和偶数来讨论):

思路二(快慢指针):

5,环形链表的约瑟夫问题_牛客题霸_牛客网 (nowcoder.com)

分析+代码实现:

6,面试题 02.04. 分割链表 - 力扣LeetCode)

分析:

思路一(修改原链表):

思路二(建立两个链表):

思路三(最蠢的!!!):      

思路四(交换指针的值):

代码实现(思路四):

7,链表的回文结构:链表的回文结构_牛客题霸_牛客网 (nowcoder.com)

分析:

  代码实现:

8,寻找两个链表的共同节点:160. 相交链表 - 力扣(LeetCode)

思路一(暴力求解):

思路二 (齐驱并驾法):

代码实现:

9,给定链表,判断是否有环:141. 环形链表 - 力扣(LeetCode)

思路(快慢指针):

代码实现:


标题开个玩笑哈哈哈哈哈哈哈

前言:

    在学完单链表(单向不带头不循环)和双向链表(双向带头循环)后我们会发现双向链表虽然结构略微复杂,但实现相同功能时相较于单链表方便了不止一星半点。但双向链表是大佬而不是咱们发明的,为了考验咱是否拥有电子智慧😂😂😂,算法题的考察对象都是单链表.以下是一些经典算法题的思路分析和代码实现:

经典题目:

1,移除链表元素 - 力扣(LeetCode)

    思路一(修改原链表): 遍历链表,将每个数值等于val的节点删除。

 优点:思路暴力容易想到 --------缺点:删除每个节点时都会影响前后两个节点,增加代码复杂度

    思路二(建立新链表):建立一个新链表,插入每个数值不等于val的节点。

代码实现

部分细节补充:

    1,新链表在获得第一个节点时比较特殊,要让头和尾两个指针相等。

    1,虽说是创建新链表,可实质上仅仅搬运了原链表的节点,因此当特殊情况(原链表的最后一个节点是要删除的节点)出现时,要做特殊处理(因为此时新链表的尾结点是原链表的倒数第二个节点,这个节点任然指向原链表中的尾结点),将新链表尾结点的next指针置为空。

2, 反转链表 - 力扣(LeetCode)

    分析:

1:先从简单的入手,题目有这种测试用例,因此首先对头结点判断,为空则直接返回。

               2:想要翻转链表,直观理解就是改变每个节点的next指针的指向,让每个节点都指向上一个节点,可这恰好是单链表的缺陷(单链表的每个节点只存有下一个节点的地址)。但是,尾结点的next指针比较特殊,指向NULL,这也是结题的突破口。

    思路(三指针法):

既然尾结点的next指针指向空,那就创建一个空节点,让头结点指向空。既然节点无法找到上一个节点,就先创建三个指针将相邻的三个节点连接起来,然后整体向后偏移。

部分细节补充:

                         1、对于循环结束条件的判断一定要通过画图来决定,切忌空想。

                         2、要仔细观察题目所给的用例,避免不必要的错误。

3, 合并两个有序链表 - 力扣(LeetCode)

分析:

     思路一(修改原链表):

如果按题目的字面要求,就是固定一个链表1(链表2也可以),让另链表2的节点与这个节点依次比较,值更小则插在链表1的相应节,值更大则插在链表2的相应节点之后。可这样每次插入后都要处理前后节点,比较麻烦。

      思路二 (建立新链表):

直接创建一个新的链表,然后比较两个链表节点的值,谁小就先尾插谁,这样思路就简单明了了。

代码实现:

4,链表的中间结点 - 力扣(LeetCode)

分析:

   思路一(遍历算长度,分奇数和偶数来讨论):

先遍历链表,算出长度,再分为奇数和偶数两种情况来寻找中间节点。

思路二(快慢指针):

定义两个从头开始的指针,一个一次走一步,另一个一次走两步,也称为快慢指针。当快指针走到空或者快指针的下下个节点为空时就会惊奇的发现慢指针的正好处在中间节点处。

5,环形链表的约瑟夫问题_牛客题霸_牛客网 (nowcoder.com)

分析+代码实现:

          由于是n个人围成一圈,且题目中有“环形链表”这一关键词,所以二话不说先创建一个环形链表(与普通的单链表类似,只不过尾结点里存放的下一个节点是链表的第一个节点)

     环形链表创建好后再来分析题目情景中的报数离开的逻辑,每一个离开的倒霉蛋都相当于被销毁的链表节点,而链表中节点的销毁势必会影响到前一个节点的指向,因此在一开始第一个人报数的时候(头结点计数)也要先保留前一个节点,由于本题是环形链表,所以先记录下在首节点前的尾结点,这也是为啥刚才创建环形链表的函数里要返回尾结点的指针。

typedef struct ListNode listnode;
 //创建节点
 listnode* ApplyNode(int a)
 {
    listnode* ret = (listnode*)malloc(sizeof(listnode));
    ret->val = a;
    return ret;
 }
 //创建环形链表
 listnode* CreateCircle(int n)
 {
     listnode* phead = NULL,* ptail = NULL;
     int i = 0;
     for(i = 1; i<=n; i++)
     {
        if(!phead)
        {
            phead = ptail =  ApplyNode(i);
        }
        else 
        {
            ptail->next = ApplyNode(i);
            ptail = ptail->next;
        }
     }
     ptail->next = phead; //将链表首尾相连
     return ptail; //返回尾结点是关键
 }

int ysf(int n, int m )
 {
    int count = 1; //用conut来模拟每个节点的报数
    //创建环形链表
    listnode* ptail = CreateCircle(n);
    //开始报数
    listnode* prev = ptail;
     listnode* pcur = prev->next;  
    while(prev->next!=prev) //当prev的next指针指向自己时,链表里仅剩一个元素
    {
       
        if(count==m)
        {
           prev->next = pcur->next;
           free(pcur);
           pcur = NULL;
           count=1; 
           pcur = prev->next;
        }
        else 
        {
            prev = pcur;
            pcur = pcur->next;
            count++;
        }
    }
    return prev->val;
}

6,面试题 02.04. 分割链表 - 力扣LeetCode)

分析:

      这是一个阅读理解题(因为出题人对题目的描述太抽象了!!!),就是把小于分界值的链表弄到分界值的前面。

思路一(修改原链表):

    遍历链表,将每个大于分界值的节点尾结点放到链表末尾。十分麻烦,因为要定义实时移动的指针、原链表的尾指针、尾插后的新的尾指针,还要销毁被移走的节点,因此不采用这种方法。

思路二(建立两个链表):

    建立一个小链表用于存放值小于分界值的节点,再建立一个大链表用于存放大于等于分界值的节点,最后再将大链表接在小链表之后。

思路三(最蠢的!!!):      

    先找到第一个大于等于x的节点用指针A记录下来,在让另一个指针B也从这个节点开始往后遍历,当指针B遇到值小于x的节点时,删除次节点并插到指针A之前,如此一直到B->next为空结束。此方法过于直接,情况最坏时同时涉及在链表中间插入和删除节点,容易晕,不推荐。

思路四(交换指针的值):

     先找到第一个大于等于x的节点用指针A记录下来,在让另一个指针B也从这个节点开始往后遍历,当指针B遇到值小于x的节点时,交换指针A和B的值,再更新A和B的指向,知道B为空结束。此方法较为巧妙,操作难度也不多,good!

代码实现(思路四):

    

typedef struct ListNode listnode;

struct ListNode* partition(struct ListNode* head, int x)
{
     if(!head) //0个节点
    {
        return NULL;
    }

     if(head->next == NULL) //一个节点
     {
        return head;
     }

     listnode *cur = head, *slow = NULL, *fast = NULL;
     //让两个指针都到值大于等于x的节点
     while(cur->val < x && cur->next!=NULL)
     {
        cur = cur->next;
     }
     slow = fast = cur;
     //
     while(fast)
     {
        if(fast->val < x)
        {
            int mid = slow->val;
            slow->val = fast->val;
            fast->val = mid;
            slow = slow->next;
        }
           fast = fast->next;
     }
   return head;
}

7,链表的回文结构:链表的回文结构_牛客题霸_牛客网 (nowcoder.com)

    

分析:

    

  代码实现:

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
typedef struct ListNode listnode;

class PalindromeList {
public:
    listnode* MidNode(listnode *A)//中间节点
{
  listnode *slow = A;
  listnode *fast = A;
  while(fast && fast->next)
  {
    slow = slow->next;
    fast = fast->next->next;
  }
  return slow; 
}

listnode* Reverse(listnode *head)//翻转链表
{
  if(head == NULL||head->next == NULL)
    {
        return head;
    }

    listnode *pre =  NULL;
    listnode *cur = head;
    listnode *later = head->next;

    while(cur)
    {
        cur->next = pre;
        pre = cur;
        cur = later;
        if(later)
        {
            later = later->next;
        }
    }
    return pre;
}

    bool chkPalindrome(ListNode* A) 
    {
        // write code here
        if(A == NULL || A->next == NULL)
        {
            return true;
        }

        listnode* mid = MidNode(A);
        listnode* rmid = Reverse(mid);
        while(rmid->next)
        {
            if(A->val != rmid->val)
            {
                return false;
            }
            A = A->next;
            rmid = rmid->next;
        }
      return true;
    }
};

8,寻找两个链表的共同节点:160. 相交链表 - 力扣(LeetCode)

思路一(暴力求解):

    将链表A的每一个节点都分别链表B的每一个节点进行比较 (A、B任意选择),当存在相同节点时,存在相交节点。此方法时间复杂度为O(N^2),效率低下,不推荐。

思路二 (齐驱并驾法):

    通过观察题目示例可知,如果让链表B从b2节点开始和链表A一起迭代,最终会相交。

代码实现:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode listnode;

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) 
{
    int lenA = 0,lenB = 0;
    listnode* heada = headA,*headb = headB;
    //计算A链表的长度
    while(heada->next)
    {
      lenA++;
      heada = heada->next;
    }
    //计算B链表的长度
    while(headb->next)
    {
        lenB++;
        headb = headb->next;
    }
    //计算俩链表的长度差
    int gap = abs(lenA-lenB);
    //利用假设法定义长短链表,方便后续操作
   listnode *longlist = headA,*shortlist = headB;
   if(lenB > lenA)
    {
        longlist = headB;
        shortlist = headA;
    }
    //让长链表先走
    while(gap--)
    {
        longlist = longlist->next;
    }
    //让两个链表同时走,判断是否有共同节点
    while(longlist!=shortlist && longlist && shortlist)
    {
        longlist = longlist->next;
        shortlist = shortlist->next;
    }
    return shortlist; //若俩链表一直没有公共节点,则会走到空
}

9,给定链表,判断是否有环:141. 环形链表 - 力扣(LeetCode)

思路(快慢指针):

    使用两个指针,慢指针一次移动一个节点,快指针一次移动两个节点,若存在环,则二者终会相遇,否则快指针会先走到空或者下一个节点为空。

代码实现:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 typedef struct ListNode listnode;

bool hasCycle(struct ListNode *head) 
{   
    if(head == NULL || head->next == NULL) //当链表为空或只有一个节点
    {
        return false;
    }

    listnode *slow = head,*fast = head->next; //定义快慢指针

    while(fast && fast->next) //如果不存在环,fast指针会先走到空
    {
        if(slow == fast) //如果存在环,slow和fast指针会相遇
        {
            return true;
        }
        slow = slow->next;
        fast = fast->next->next;
    }
    return false;
}

  快满指针一定相遇的证明:

  

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值