关于链表相关的OJ题

✨✨✨专栏:数据结构     

          🧑‍🎓个人主页SWsunlight

一、 OJ题 返回倒数第K个节点

 1、遍历链表一遍:用2个指针,pheadptail先让ptail先走k步,然后让2个指针一起走,快的走到NULL即可

就是数学问题:第n-k个就是倒数第k个

2、也可以遍历一遍算出节点个数n,然后倒数第k个就是节点n-k,在遍历一次即可

代码:

int kthToLast(struct ListNode* head, int k){
    struct ListNode*phead=head;
    struct ListNode*ptail=head;
    //先让快的走k步
    while(k--)
    {
        ptail=ptail->next;
    }
    while(ptail)
    {
         ptail=ptail->next;
         phead=phead->next;
    }
    return phead->val;

}

falst 和 slow

二、OJ题 合并2个有序链表

 注意:题目说的链表是有序的,那么我们可以建立一个哨兵位 然后将各个节点放到哨兵位的后面 进行有序排序即可:

//改名,便于使用
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    //先判空:
    if(list1==NULL)
        return list2;
    if(list2==NULL)
        return list1;
    ListNode*head;
    ListNode*tail;
    ListNode*l1 = list1;
    ListNode*l2 = list2;
    head = tail = (ListNode*)malloc(sizeof(ListNode));
    //只要有一个为NULL就出来;
    while(l1&&l2)
    {
        //小的先放入后面
        if(l1->val<l2->val)
        {
            tail->next = l1;
            tail=tail->next;
            l1 = l1->next;
        }
        else
        {
            tail->next = l2;
            tail = tail->next;
            l2 = l2->next;
        }
    }
    //出循环2种情况:l1和l2都为NULL
    //              l1或者l2 一个为NULL]
    //不要用循环了,因为其中一个为NULL,tail后面直接就是加上了另一个链表的剩下的节点
    if(l1)
    {
        tail->next = l1;
        tail = tail->next;
    }
    if(l2)
    {
        tail->next = l2;
        tail = tail->next;
    }
    ListNode*ret = head->next;
    //申请的空间还给操作系统
    free(head);
    head = NULL;
    return ret;
}

 三、相交链表

关于这个题,可以先考虑是否相交遍历(同时记录2个指针的长度)对最后一个节点地址进行判断是否相等(相等就是说明这个环相交),若是不相交直接结束程序了;

若是相交,我们就得继续考虑:发现了么,若是相交,看头节点的位置,2个链表会有一个距离差,这个时候我们可以长的链表先走完距离差,然后2个链表一起遍历,同速度一定相遇

注意:我们用地址那判断,不用节点里的数据是因为无法保证后前面数据有相同的,地址更保险

typedef struct ListNode ListNode;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    ListNode*l1=headA;
    ListNode*l2=headB;
    //记录节点长度
    int k = 0;
    int n = 0;
    //l1、l2的位置不能到NULL,不然这不就一定相等了(链表的最后一个节点指向的下一个地址为NULL)
    while(l1&&l1->next)
    {
        l1 = l1->next;
        k++;
    }
    while(l2&&l2->next)
    {
        l2 = l2->next;
        n++;
    }
    //若是最后一个节点不相等 说明不为环
    if(l1!=l2)
    {
        return NULL;
    }
    //为环继续进行;
    ListNode* phead1 = headA;
    ListNode* phead2 = headB;
    //假设法:1放长的,2放小的链表
    if(k<n)
    {
        phead1 = headB;
        phead2 = headA;
    }
    //求2个的链表的长度差
    //abc为绝对值函数,返回绝对值
    int key = abs(n-k);
    while(key--)
    {
        phead1=phead1->next;
    }
    //同时遍历,相同时结束
    while(phead1!=phead2)
    {
        phead1 = phead1->next;
        phead2 = phead2->next;
    }
    return phead1;

}

用到了假设法,还有 绝对值函数:abs()函数

四、链表的中间节点

快慢指针即可,快的指针到到尾节点时,慢指针刚好走了一半;不要考虑节点个数为奇数还是偶数,因为题目说了若是2个中间节点返回第2个,如下:当快指针结束会有2种情况,刚好跟节点个数有关 记住节点个数:n      中间节点的公式: n /  2   表示从第一个节点的下一个节点开始数,第几个节点就是中间节点

typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) {
    ListNode* fast = head;
    ListNode* slow = head;
    while(fast&&fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;
    }
    return slow;
}

五、反转链表

看到题目可能会想到创建一个新链表,将原链表的节点以头插的形式插入到新链表中,但是我今天想用的是不创建新的链表,使用迭代的方式进行实现反转

将l1当做尾节点依次类推  

用到了3个指针  l3指向NULL,l2用来保存下个节点的地址,l1用来修改所指向节点的next的地址   

移动步骤如下:1、修改l1的next指向l3

                         2、将l3移动到l1的位置

                         3、将l1移动到l2的位置

                         4、将l2后移一位

结束条件 我们将l2作为条件,当l2指向空时结束,如下:但是l1的next是还没有变的,使用if语句判断 进行修改 即可,最后返回 l1 刚好是头节点的地址

typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) {
    //判NULL,若是为空则直接返回
    if(head==NULL)
    {
        return head;
    } 
    //l2保存下一个节点地址,l1修改其所在节点的next 
    ListNode*l1 = head;
    ListNode*l2 = head->next;
    //存储l1->next的节点,开始时是NULL,因为头节点变成尾节点的next指向的内容为NULL
    ListNode*l3 = NULL;
    while(l2)
    {
        l1->next = l3;
        l3 = l1;
        l1 = l2;
        l2 = l2->next;
        //当l2NULL;l1在尾节点,直接修改尾节点的next内容,
        if(l2==NULL)
        {
            l1->next = l3;
        }
    }
    return l1;
}

注意 :要判空,不然l2赋值时就非法操作了(对空指针进行解引用)

方法二、递归法:

思路:将最后一个节点当成头节点,将前一个节点插到后一个节点的next处,将前一个节点的next指向NULL;

每个函数的头节点head恰好都对应的节点,然后将5的尾插入前一个节点,还有NULL也不要忘记插;

 //递归
struct ListNode* _reverseList(struct ListNode* head)
{
    //2种情况结束,传的头本身就是空
    if(head==NULL||head->next==NULL)
    {
        return head;
    }
    struct ListNode*nwhead=_reverseList(head->next);
    head->next->next=head;
    head->next=NULL;
    return nwhead;

}


struct ListNode* reverseList(struct ListNode* head){
    return _reverseList(head);
}

 六、链表的回文结构:

快慢指针法:

看到回文结构,我们可以用到中间节点和反转链表2个方法结合,CV一下;我们将链表的中间节点后面的节点进行反转,然后进行判断

这里取的是第3个节点进行反转,但是因为我么的第2个节点next指针指向的是第三个,我们没有对它修改,所以拆开以后就是如下的样子:

无论奇数还是偶数都没有影响

typedef struct ListNode ListNode;

class PalindromeList {
  public:
   
    struct ListNode* middleNode(struct ListNode* head) {
        ListNode* fast = head;
        ListNode* slow = head;
        while (fast && fast->next) {
            fast = fast->next->next;
            slow = slow->next;
        }
        return slow;
    }
    struct ListNode* reverseList(struct ListNode* head) {
        //判NULL,若是为空则直接返回
        if (head == NULL) {
            return head;
        }
        //l2保存下一个节点地址,l1修改其所在节点的next
        ListNode* l1 = head;
        ListNode* l2 = head->next;
        //存储l1->next的节点,开始时是NULL,因为头节点变成尾节点的next指向的内容为NULL
        ListNode* l3 = NULL;
        while (l2) {
            l1->next = l3;
            l3 = l1;
            l1 = l2;
            l2 = l2->next;
            //当l2NULL;l1在尾节点,直接修改尾节点的next内容,
            if (l2 == NULL) {
                l1->next = l3;
            }
        }
        return l1;
    }

    bool chkPalindrome(ListNode* A) {
        //pet接受后半部分的反转链表
        ListNode* pet = middleNode(A);
        ListNode*A1 =A;
        ListNode*A2 =reverseList(pet);
        //进行比较
        while(A2)
        {
            if(A2->val!=A1->val)
            {
                return false;
            }
            A2=A2->next;
            A1=A1->next;
        }
        return true;


    }
};

我在牛客网写的这个题目,因为c++是兼容c的牛客题没有c语言这个环境的选项,就直接在c++里面写了

七、移除链表元素: 

3个指针 :

将节点取出来创建成一个“新”的链表(一个记录头一个记录尾)再来一个遍历链表,遍历到NULL时结束循环;以上面题目提供的例子为例的话,当我将所有节点取出重新组合后,为下图第一个图形,发现会有一个6没去掉,因为节点指向下一个节点的next没有修改,所以最后循环结束应该要加一个判断条件 尾指针是否为NULL,不是NULL,就给它next赋值NULL;

typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val) {
    //先判空
    if(head==NULL)
    {
        return head;
    }
    //记录头结点
    ListNode* phead= NULL;
    //记录尾节点
    ListNode*ptail = NULL;
    //用来遍历:
    ListNode*pcur=head;
    while(pcur)
    {
       if(pcur->val!=val)
       {
        //是否为空链表
        if(phead==NULL)
        {
            phead= ptail=pcur;
        }
        else
        {
            ptail->next=pcur;
            ptail = ptail->next;
        }
       }
       pcur = pcur->next;
    }
    //出循环以后,若是ptail不是空,将下一个地址给空
    if(ptail)
    {
        ptail->next=NULL;
    }
 
    return phead;
}

 八、随机链表的复制

错位插入,复制每个节点,然后和原链表以如下方式连接起来,形成一个新的链表

如下:要向堆区申请空间,要创建一个节点

给random分配

typedef struct Node Node;
struct Node* copyRandomList(struct Node* head) {
    //创立一个新的节点
    Node*phead = head;
    //phead为NULL时结束
    while(phead)
    {
        //申请节点,创建新的节点,需要申请空间
        Node*node = (Node*)malloc(sizeof(Node));
        node->val = phead->val;
        //我的节点next为head的下一个节点地址
        node->next=phead->next;
        //初始情况:
        node->random = NULL;
        //phead的next指向node
        phead->next=node;
        phead = node->next;
    }
    //对上面的部分random进行分配
    Node* cal = head;
    while(cal)
    { 
        //cal的下一个节点就是要分配的节点
        Node* pov = cal->next;
        if(cal->random==NULL)
        {
            pov->random=NULL;
        }
        else
        {
            pov->random = cal->random->next;
        }
        cal = pov->next;
    }
    //拆开  //裁掉
    cal = head;
    //尾
    Node* povhead=NULL;
    //头不动
    Node* nude = NULL;
    while(cal)
    {
    //判空
     if(povhead==NULL)
     {
        povhead = nude = cal->next;
     }
     else{
        povhead->next = cal->next;
        povhead = povhead->next;

     }

     //恢复原链表
     cal->next = povhead->next;
     cal = cal->next;
        
    }

    return nude;

}

 

  • 14
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值