单链表经典算法题

反转链表,顾名思义即为将链表颠倒,有一个非常容易想到的思路,就是你创建一个新链表,然后逐个遍历原链表的节点,逐个头插进新链表中。但我今天不讲这个方法,我要为大家讲解一种在原链表上原地反转的方法。

其实说插入链表总共要考虑什么呢,新节点的指向,前一个结点的指向;那么说删除节点又要考虑什么呢,其实就是前一个节点的指向;说来容易,那我们这里的反转,无非要考虑的情况也就那么几种,首先我们要找到当前节点,这是肯定的吧,光有当前节点肯定不够,我们还需要当前节点的前一个结点和后一个节点,前一个结点你能理解,因为你要修改当前节点的指向,改成原先的前一个结点,那后一个节点是为啥呢?其实很好想,当你将当前节点的指向修改后你已经无法找到原来的下一个节点了,所以必须提前拿到后手动赋值以免循环无法进行。那么赋值又该怎么赋?无非是整体后移一位,即prev指向上一次的now,now指向next,next指向新的next;那么循环啥时候截止?当now为空时,代表你已经完成任务,链表里已经没有元素了,此时截止即可。但是还有细节不要忘记,就是我们是要改变头节点的朝向的,一定记得最后将prev的值赋给头节点,为啥是prev?还记得我们说now为空时循环终止条件吗,prev自然是第一个元素。照例考虑临界情况,首先链表只有一个元素怎么办?直接返回这个节点即可,那如果链表为空呢?那直接返回NULL就可以了吧。代码实现如下

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 typedef struct ListNode LN;
struct ListNode* reverseList(struct ListNode* head) {
   if(head == NULL)
   {
        return NULL;
   }
    LN* pnow=head->next;
    LN* prev=head;
    head->next=NULL;
    while(pnow)
    {
        LN* next=pnow->next;
        pnow->next=prev;

        prev=pnow;
        pnow=next;
    }
    return prev;
}

接下来第二题

移除链表元素,跟上一篇我讲的移除顺序表元素很像,但其实更加简单了,因为这个无非就是在考你链表指定位置的删除,因为不同于数组,链表删除是十分便利的,根本就涉及不到元素的挪动问题,所以其实这道题没啥好说的,我直接放代码

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 typedef struct ListNode LN;

struct ListNode* removeElements(struct ListNode* head, int val) {
    LN* pnow=head;
    LN* prev=NULL;
    while(pnow)
    {
        LN* next=pnow->next;
        if(pnow->val==val)
        {
            if(pnow==head)
            {
                head=next;
            }
            if(prev)
            {
                 prev->next=next;
            }
           
        }
        else
        {
            prev=pnow;
        }
        pnow=next;
        
    }   
    return head;
}

第三题

中间节点,这里我要给大家介绍一个非常重要的思想,快慢指针,快指针走两步慢指针走一步,最后快指针无法再走时慢指针一定指向中间节点,这就跟物理速度差不多,你速度二倍时间一样,路程可不就是二倍关系吗。

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 typedef struct ListNode LN;
struct ListNode* middleNode(struct ListNode* head) {
    LN* fast=head;
    LN* slow=head;
    while(fast&&fast->next)
    {
        slow=slow->next;
        fast=fast->next->next;
    }
    return slow;
}

第四题

这道题我选择创建新链表逐个尾插的方式,同样是定义两个指针遍历两个链表,然后定义新的头节点,但是这里注意,创建新链表只是存在于逻辑上,并不是真的开辟了一块空间存储数个节点,而是在原链表上进行操作,只是形如建立新链表的样子,待到一方为空则直接把非空链表的剩余节点尾插即可,代码如下

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 typedef struct ListNode LN;

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    LN* l1=list1;
    LN* l2=list2;
    if((l1==NULL && l2!=NULL)||(l1!=NULL && l2==NULL))
    {
        return l1==NULL?l2:l1;
    }
    LN* newhead=NULL;
    LN* newtail=NULL;
    while(l1&&l2)
    {
        if(l1->val < l2->val)
        {
            if(newhead==newtail)
            {
                if(newhead==NULL)
                {
                    newhead=l1;
                    newtail=newhead;
                }
                else
                {
                    newtail=l1;
                    newhead->next=newtail;
                }
            }
            else
            {
                newtail->next=l1;
                newtail=newtail->next;
            }
            l1=l1->next;

        }
        else
        {
             if(newhead==newtail)
            {
                if(newhead==NULL)
                {
                    newhead=l2;
                    newtail=newhead;
                }
                else
                {
                    newtail=l2;
                    newhead->next=newtail;
                }
            }
            else
            {
                newtail->next=l2;
                newtail=newtail->next;
            }
            l2=l2->next;
        }
    }
    if(l1==NULL)
    {
        while(l2)
        {
            newtail->next=l2;
            newtail=l2;
            l2=l2->next;
        }
    }
    else
    {
        while(l1)
        {
            newtail->next=l1;
            newtail=l1;
            l1=l1->next;
        }
    }
    return newhead;
}

第五题

这应该是最复杂的一道题,但是并不算难,只是步骤比较多,我们捋一下这道题,首先他说围成一圈,其次我们需要不停的去淘汰节点,或者说删除节点,那自然不能使用数组,也不能使用普通的单链表,因为达不到首尾相接的效果,这时候就要想到循环链表,即尾节点指向头节点以达到循环效果,那么解决了选用数据结构的问题,接下来开始正式梳理这道题,看到这道题的淘汰制,一定第一时间想到删除,单链表节点的删除需要三个节点,循环链表其实也是一样,在这里区别不大,因为我们是单向循环,这是底层逻辑,然后针对这道题,我们要考虑报数的机制,从1开始,逐个遍历,遍历一个节点加1,达到目标后停止,并删除相应节点,然后就要接着遍历,从1接着开始,循环往复直到链表中只剩一个节点,这里的删除我要简单说一句,删除节点后,prev是不能动的,你想,你删掉了这个节点,那么下一个节点的上一个节点不久改成prev了吗,其他的部分其实没什么特别需要注意的地方,具体可以参考下面代码

cpp 复制代码
/**
 * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
 *
 * 
 * @param n int整型 
 * @param m int整型 
 * @return int整型
 */

typedef struct ListNode LN;
int ysf(int n, int m ) {
    // write code here
    //成环
    LN* newhead=(LN*)malloc(sizeof(LN));
    LN* pcur=newhead;
    LN* ptail=newhead;
    newhead->val=1;
    newhead->next=ptail;
    for(int i=2;i<=n;i++)
    {
        LN* newnode=(LN*)malloc(sizeof(LN));
        newnode->val=i;
        pcur->next=newnode;
        pcur=pcur->next;
    }
    pcur->next=newhead;
    ptail=pcur;

    LN* prev=ptail;
    LN* pnow=newhead;
    LN* next=newhead->next;
    int cnt=0;
    while(pnow!=prev || pnow!=next)
    {
        cnt++;
        if(cnt==m)
        {
            prev->next=next;
            free(pnow);
            pnow=next;
            next=pnow->next;
            cnt=0;
        }
        else 
        {
            prev=pnow;
            pnow=next;
            next=pnow->next;
        }
    }
    return pnow->val;

}
相关推荐
小O的算法实验室1 小时前
IEEE TCYB,着色旅行商问题:模型、求解与应用
算法
h_a_o777oah1 小时前
【图论】网络流:Dinic 算法模板实现原理及解题技巧
c++·算法·图论·acm·网络流·最小割·dinic算法
daxiangxm1 小时前
【趣味休息】“围住小猫在线玩”-“困住小猫”,又回来了
数据结构·人工智能·vscode·github·php
我不会起名字3222 小时前
一天一道力扣Hot100(37):深度优先算法--括号生成
java·数据结构·c++·后端·python·算法·go
foolishlee2 小时前
SCRAM-SHA-256
数据库·算法·postgresql
Rabitebla2 小时前
【Linux 系统编程】权限(一):身份、提权,和那 9 个权限位
linux·数据结构·c++·算法
-dzk-2 小时前
【回溯】LC 22.括号生成
算法·回溯
杜 硕3 小时前
顺序表算法
数据结构·算法