【链表经典OJ-中】

★★★★★个人专栏《C语言》** 《数据结构-初阶》 ★★★★★**

欢迎各位大佬交流!!!

通过对经典链表OJ题目的练习,不仅能加深对链表的理解,更能体会链表的精妙之处!

目录

5、环形链表的约瑟夫问题

一、创建链表

二、处理报数

6、分割链表

思路一:

思路二:

7、返回倒数第k个节点

8、链表的回文结构


5、环形链表的约瑟夫问题

看完题目之后,显然这是环形链表问题;

首先创建n个节点的链表,并且尾节点的next指向头节点;

接着开始报数,一直到最后一个人,返回其val;

一、创建链表

为了保证链表的存在,我们必须使用malloc去开辟一块空间,创建头指针;

那么尾指针需不需要malloc?

不需要!!!

创建对应的结构体指针即可,初始化时将tial指向head

cpp 复制代码
    struct ListNode* head = (struct ListNode*)malloc(sizeof(struct ListNode));
    head->val = 1;
    head->next = NULL;
    struct ListNode* tail = head;

接着创建剩下的节点

cpp 复制代码
    for (int i = 2; i <= n; i++)
    {
        struct ListNode* newnode = (struct ListNode*)malloc(sizeof(struct ListNode));
        newnode->val = i;
        newnode->next = NULL;
        tail->next = newnode;
        tail = newnode;
    }

跳出循环后,将tail的next指向head,这样就完成了循环链表的构建;

最终返回head

二、处理报数

首先需要创建cur指针用来遍历循环链表;

当报数为m时,需要删除当前节点,删除当前节点需要当前节点的前一个节点;

因此我们在创建prev,但是怎样进行初始化呢?

直接将prev = cur吗?

那这样还需要额外的判断,不如将创建链表函数返回tail指针,这样prev和cur均能正常赋值

接着就是进行判断,如果当前节点报数为m,就删除当前节点,否则就将prev、cur均向前走一步;

循环结束的条件就是cur->next == cur,此时表示链表中仅有一个元素

最终直接返回cur->val即可

cpp 复制代码
struct ListNode* CreateList(int n)
{
    //先创建一个节点
    struct ListNode* head = (struct ListNode*)malloc(sizeof(struct ListNode));
    head->val = 1;
    head->next = NULL;
    struct ListNode* tail = head;

    //再创建剩下的节点
    for (int i = 2; i <= n; i++)
    {
        struct ListNode* newnode = (struct ListNode*)malloc(sizeof(struct ListNode));
        newnode->val = i;
        newnode->next = NULL;
        tail->next = newnode;
        tail = newnode;
    }
    tail->next = head;

    return tail;
}

int ysf(int n, int m)
{
    // write code here
    //创建环形链表

    struct ListNode* prev = CreateList(n);
    struct ListNode* cur = prev->next;

    int cnt = 1;
    while (cur->next != cur)
    {
        while(cnt != m)
        {
            cnt++;
            prev = cur;
            cur = cur->next;
        }
        cnt = 1;
        struct ListNode* next = cur->next;
        prev->next = next;
        free(cur);
        cur = next;
    }
    return cur->val;
}

6、分割链表

思路一:

很容易想到新建一个链表,将小于x的进行头插,大于等于x的进行尾插;

首先创建出虚拟头节点,尾指针以及遍历原链表所需的cur指针

接着就开始遍历原链表

如果cur->val < x,就进行头插,但第一次头插时,需要更新tail的指向,将tail改为指向cur节点的指针;

后续在头插操作中便不再需要更新tail,因为头插之后,新链表的尾指针也不变;

如果cur->val >= x,就得进行尾插,尾插是否也需要对第一次进行特殊处理呢?并不需要!

因为尾插之后尾节点自动更新成新链表中的最后一个节点;

最后,一定要记得将尾指针的next置为空,否则程序就会超时

cpp 复制代码
struct ListNode* partition(struct ListNode* head, int x)
{
    //思路一:新建链表,小于x头插,大于等于x尾插
    struct ListNode* cur = head;
    struct ListNode* dummyhead = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* tail = dummyhead;
    dummyhead->next = NULL;
    while(cur)
    {
        struct ListNode* next = cur->next;
        if(cur->val < x)
        {
            if(tail == dummyhead)
            {
                //第一次头插,更改尾节点
                tail = cur;
            }
            //头插到新链表中
            cur->next = dummyhead->next;
            dummyhead->next = cur;
        }
        else
        {
            //尾插即可
            tail->next = cur;
            tail = cur;
        }
        cur = next;
    }
    if(tail != NULL)
    tail->next = NULL;

    return dummyhead->next;   
}

思路二:

思路一中我们创建了一个链表,实现了对链表的分割;

那如果大胆一点,直接创建两个链表会怎么样?

小链表用来处理cur->val < x 这种情况;直接尾插到小链表中

大链表用来处理cur->val >= x 这种情况,直接尾插到大链表中

最后将小链表的尾节点与大链表的头节点相连,再将大链表的尾节点的next置为空;

返回小链表的head即可

实际上思路二的代码要比思路一简单很多

cpp 复制代码
    //创建两个链表
    struct ListNode* smallhead = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* smalltail = (struct ListNode*)malloc(sizeof(struct ListNode));
    smallhead = smalltail = NULL;
    struct ListNode* bighead = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* bigtail = (struct ListNode*)malloc(sizeof(struct ListNode));    
    bighead = bigtail = NULL;

    struct ListNode* cur = head;
    while(cur)
    {
        struct ListNode* next = cur->next;
        if(cur->val < x)
        {
            //尾插到小链表中
            if(smalltail == NULL)
            {
                //第一次尾插
                smallhead = smalltail = cur;
            }
            else
            {
                //直接尾插
                smalltail->next = cur;
                smalltail = cur;
            }
        }
        else
        {
            //尾插到大链表中
            if(bigtail == NULL)
            {
                bighead = bigtail = cur;
            }
            else
            {
                //直接尾插
                bigtail->next = cur;
                bigtail = cur;
            }
        }
        cur = next;
    }

此时,我们需要连接两个链表

在连接前一定要进行判空,那我们直接提前处理;

如果小链表的尾指针为空,说明原链表的每个节点的val均 >= x,因此直接返回大链表的头指针即可

反之,直接返回小链表的头指针即可;

如果没有返回,那就连接两个链表,最终返回小链表的头节点

cpp 复制代码
struct ListNode* partition(struct ListNode* head, int x)
{
    //创建两个链表
    struct ListNode* smallhead = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* smalltail = (struct ListNode*)malloc(sizeof(struct ListNode));
    smallhead = smalltail = NULL;
    struct ListNode* bighead = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* bigtail = (struct ListNode*)malloc(sizeof(struct ListNode));    
    bighead = bigtail = NULL;

    struct ListNode* cur = head;
    while(cur)
    {
        struct ListNode* next = cur->next;
        if(cur->val < x)
        {
            //尾插到小链表中
            if(smalltail == NULL)
            {
                //第一次尾插
                smallhead = smalltail = cur;
            }
            else
            {
                //直接尾插
                smalltail->next = cur;
                smalltail = cur;
            }
        }
        else
        {
            //尾插到大链表中
            if(bigtail == NULL)
            {
                bighead = bigtail = cur;
            }
            else
            {
                //直接尾插
                bigtail->next = cur;
                bigtail = cur;
            }
        }
        cur = next;
    }
    //连接两个链表
    if(smalltail == NULL) return bighead;
    if(bigtail == NULL) return smallhead;
    smalltail->next = bighead;
    bigtail->next = NULL;

    return smallhead;
}

7、返回倒数第k个节点

这道题如果之前没做过的话,我觉得不太好想;

同样使用快慢指针,快指针先走差距步,也就是先走k步,然后再同时走;

当快指针走到为空时,慢指针就走到了倒数第k个节点的位置

最终返回慢指针的val即可

cpp 复制代码
int kthToLast(struct ListNode* head, int k) 
{
    struct ListNode* fast = head;
    struct ListNode* slow = head;
    //fast先走差距步
    while(k--)
    {
        fast = fast->next;
    }
    //再同时走
    while(fast)
    {
        slow = slow->next;
        fast = fast->next;
    }
    return slow->val;
}

8、链表的回文结构

看到时间复杂度为O(n),额外空间复杂度为O(1),表明我们只能遍历一遍链表,并且只能创建变量,无法使用辅助数组等;

那么我们先找到链表的中间节点,其实也就是在《经典链表OJ-上》中出现过的题;

然后将以链表中间节点为头节点的链表进行逆置;

假如以1-2-3-2-1为例;

逆置之后由于前半段的2节点的下一个节点是3节点,那么正好符合一一对应;

第一步前半段1和后半段1进行比较;

第二步前半段2和后半段2进行比较;

第三步前半段3和后半段3进行比较;

再走一步,就走到空,循环结束

另一个1-2-1-2偶数节点链表和上述同理

当链表节点为偶数个时,rmid就会先走到空;

因此循环结束的条件就是只要A或者rmid有一个为空就直接结束

总结一下:

a、先找到中间节点

b、接着逆置以中间节点为头节点的链表

c、进行比较

cpp 复制代码
/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
class PalindromeList {
public:

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

struct ListNode* reverseList(struct ListNode* head)
{
    //逆置从head开始的链表
    struct ListNode* cur = head;
    struct ListNode* prev = NULL;
    while(cur)
    {
        struct ListNode* next = cur->next;
        cur->next = prev;
        prev = cur;
        cur = next;
    }
    //cur的前一个元素即为逆置链表后的头节点
    return prev;
}

    bool chkPalindrome(ListNode* A)
    {
        // write code here
        struct ListNode* mid = middleNode(A);
        struct ListNode* rmid = reverseList(mid);

        while(rmid && A)
        {
            if(rmid->val != A->val) return false;
            rmid = rmid->next;
            A = A->next; 
        }
        return true;
    }
};

如有不足之处恳请指出!!!

谢谢大家!!!

相关推荐
一江寒逸2 小时前
数据结构与算法之美:绪论——构建算法思维的基石
数据结构·算法
满天星83035772 小时前
【Linux/多路复用】select
linux·运维·服务器·c语言·c++
可乐要加冰^-^2 小时前
Vscode、Pycharm快速配置Claude、CodeX
数据结构·深度学习·算法·语言模型·自动驾驶
mount_myj2 小时前
按位与【C语言】
c语言
AI人工智能+电脑小能手2 小时前
【大白话说Java面试题】【Java基础篇】第5题:HashMap的底层原理是什么
java·开发语言·数据结构·后端·面试·hash-index·hash
小成202303202652 小时前
数据结构(整理常见结构总结到树层级)
java·c语言·数据结构·c++·链表
睡觉就不困鸭2 小时前
第10天 删除有序数组中的重复项
数据结构·算法
wengqidaifeng3 小时前
数据结构:排序(下)---进阶排序算法详解
数据结构·算法·排序算法
wengqidaifeng3 小时前
数据结构:排序(上)---基础排序算法详解
数据结构·算法·排序算法