链表基础练习(C语言实现)

目录

链表练习

移除链表元素

反转链表

链表的中间节点

倒数第k个节点

合并两个有序链表

分割链表

回文链表

相交链表

环形链表

环形链表Ⅱ

随机链表的复制

环形约瑟夫问题


链表练习

移除链表元素

题目链接:203. 移除链表元素 - 力扣(LeetCode)

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点

思路解析:

  • 直接删除法

直接删除方法与链表的删除节点实现思想类似,但是需要注意,直接删除方法需要考虑第一个节点是不是要被删除的节点,因为此时需要更改指向第一个元素的指针head,并且因为单链表不能向前访问,所以如果第一个节点就是需要删除的节点,那么prev指针为空不能直接解引用

  • 尾插法

尾插法:将不等于val的节点进行尾插,但不是重新创建一个链表进行尾插操作

使用两个指针,一个phead用于指向新的链表的头,一个指针ptail指向新的链表的尾,之所以定义两个指针而不只是单单定义一个phead或者ptail是因为只定义一个phead,那么每次尾插时都需要重新找尾结点

参考答案:

  1. 直接删除法
/*
 * @lc app=leetcode.cn id=203 lang=c
 *
 * [203] 移除链表元素
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode SLN;
struct ListNode* removeElements(struct ListNode* head, int val) {
    //直接删除方法
    SLN* prev = NULL;
    SLN* cur = head;
    while(cur)
    {
        if(cur->val == val)
        {
            //如果第一个位置就是需要删除的数值
            if(prev == NULL)
            {
                //prev为空就执行删除代表第一个数值就是需要删除的数值
                cur = head->next;
                free(head);
                head = cur;//更新头指针
            }
            else
            {
                //prev不为空时执行删除代表第一个元素不是需要删除的元素
                prev->next = cur->next;
                free(cur);
                cur = prev->next;//更新cur指针
            }
        }
        else
        {
            //不等于需要删除的数值时就不删除
            //移动prev和cur
            prev = cur;
            cur = cur->next;
        }
    }
    return head;
}
// @lc code=end
  1. 尾插法
/*
 * @lc app=leetcode.cn id=203 lang=c
 *
 * [203] 移除链表元素
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode SLN;
struct ListNode* removeElements(struct ListNode* head, int val) {
    //将不等于val的数值进行尾插
    SLN* ptail = NULL;
    SLN* phead = NULL;
    SLN* cur = head;
    while (cur)
    {
        if(cur->val != val)
        {
            //第一个节点时进行添加
            if(ptail == NULL)
            {
                phead = cur;
                ptail = cur;
            }
            //不是第一个节点进行尾插
            else
            {

                ptail->next = cur;
                ptail = ptail->next;
            }
            cur = cur->next;
            ptail->next = NULL;//在上一句的下面,因为此时cur和ptail都指向同一个节点,先置为空导致cur无法继续遍历
        }
        else
        {
            //等于val时删除
            SLN* del = cur;
            cur = cur->next;
            free(del);
        }
    }
    return phead;
}
// @lc code=end

反转链表

题目链接:206. 反转链表 - 力扣(LeetCode)

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

思路解析:

直接在原链表的基础上修改,先让指向旧链表的头的cur指针移动到下一个节点,再将prev指针的值给新头phead,再移动prev,最后移动phead,移动phead时需要注意cur是否为空,如果cur为空,此时移动phead会使phead也变为空

参考答案:

/*
 * @lc app=leetcode.cn id=206 lang=c
 *
 * [206] 反转链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode SLN;
struct ListNode* reverseList(struct ListNode* head) {
    if(!head)
    {
        return head;
    }

    SLN* prev = NULL;
    SLN* phead = head, *cur = head;
    while(cur)
    {
        cur = cur->next;
        phead->next = prev;
        prev = phead;
        // if(!cur)//!cur代表假变真,真变假,如果写在循环内则会出现:当cur不为空时(真),则不执行if语句(因为真已经变为假)
        // {
        //     phead = cur;
        // }
        if(cur != NULL)
        {
            phead = cur;
        }
    }
    return phead;
}
// @lc code=end

链表的中间节点

题目链接:876. 链表的中间结点 - 力扣(LeetCode)

给你单链表的头结点 head ,请你找出并返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。

思路解析:

使用快慢指针法,快指针一次走两步,慢指针一次走一步,最后返回慢指针即可,注意循环条件是fast && fast->next,而不是fast->next && fast,因为对于偶数个节点的链表来说,在走到了fast需要走到空才停止,而奇数个节点的链表走到next节点为空停止即可,如果是第二种,那么当链表为偶数个节点的链表,当slow走到第二个中间节点时,fast已经在空的位置,此时对于循环条件fast->next && fast来说,会先判断fast->next,由于此时fast == NULL,故此时会出现空指针解引用错误

参考答案:

/*
 * @lc app=leetcode.cn id=876 lang=c
 *
 * [876] 链表的中间结点
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode SLN;
struct ListNode* middleNode(struct ListNode* head) {
    //快慢指针法
    SLN* fast = head, *slow = head;
    while(fast && fast->next)//注意先判断fast是否为空,再判断fast->next是否为空,反过来会使偶数个数节点的链表出现空指针解引用问题
    {
        //慢指针走一步,快指针走两步
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}
// @lc code=end

倒数第k个节点

题目链接:链表中倒数第k个结点_牛客题霸_牛客网

输入一个链表,输出该链表中倒数第k个结点

思路解析:

本题与上一题类似,同样使用快慢指针法来进行解决,先让快指针先走k步,使快指针和慢指针拉开差距,此时慢指针和快指针相差k个单位长度(k-1个节点,要求第k个节点,将当前节点加上k-1即为第k个节点),如果快指针此时不为空,则证明存在倒数第k个节点,再让快指针和慢指针一起走,当快指针走到空时,由于快指针和慢指针中间相差k-1个节点,此时慢指针指向的节点即为倒数第k个节点

参考答案:

typedef struct ListNode SLN ;

SLN* FindKthToTail(SLN* head, int k) {
    if(!head || k <= 0)
    {
        return NULL;
    }
    SLN* slow = head, *fast = head;
    while (k--) 
    {
        if (fast)
        {
            fast = fast->next;
        }
        else 
        {
            return NULL;
        }
    }
    while (fast) 
    {
        slow = slow->next;
        fast = fast->next;
    }

    return slow;
}

合并两个有序链表

题目链接:21. 合并两个有序链表 - 力扣(LeetCode)

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

思路解析:

本题的思想和移除链表元素题目的思想一致,只是多了一个比较的过程,并且不同于顺序表的合并,链表的合并两个链表都需要考虑到是否走完

参考答案:

/*
 * @lc app=leetcode.cn id=21 lang=c
 *
 * [21] 合并两个有序链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode SLN;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    //当某一个链表为空时,直接返回另外一个链表
    if(!list1)
    {
        return list2;
    }
    if(!list2)
    {
        return list1;
    }

    SLN* head = NULL;
    SLN* tail = NULL;
    //当二者不为空时进行尾插
    while(list1 && list2)
    {
        if(list1->val < list2->val)
        {
            //当链表的头为空时,直接添加
            if(!head)
            {
                head = tail = list1;
            }
            else
            {
                //当链表的头不为空时,进行尾插
                tail->next = list1;
                tail = tail->next;
            }
            list1 = list1->next;//移动到下一个节点比较
        }
        else
        {
            //当链表的头为空时,直接添加
            if(!head)
            {
                head = tail = list2;
            }
            else
            {
                //当链表的头不为空时,进行尾插
                tail->next = list2;
                tail = tail->next;
            }
            list2 = list2->next;//移动到下一个节点进行比较
        }
    }

    //此时可能存在某一个节点还未走完
    if(list1)
    {
        tail->next = list1;
    }
    if(list2)
    {
        tail->next = list2;
    }

    return head;
}
// @lc code=end

分割链表

题目链接:面试题 02.04. 分割链表 - 力扣(LeetCode)

给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。
你不需要 保留 每个分区中各节点的初始相对位置。

思路解析:

本题可以考虑将链表分为两部分,第一个部分由小于特定值x的节点组成,第二个部分由大于或等于特定值x的节点组成,处理好这两个部分的头和尾最后再将这两部分链接即可

基本思路:不带头单向链表

利用尾插的思路,定义四个指针,分别为指向大于或等于x的数值的大链表greaterHead和greaterTail和指向小于x数值的小链表lessHead和lessTail,定义cur指针变量原链表,遇到大于或者等于x的尾插到大链表,遇到小于x的尾插到小链表,注意第一个节点需要单独处理,因为四个指向链表的指针本质只是一个空指针

优化:带头双向链表

带头双向链表优化了四个指针为空的问题,为四个指针单独开辟两个空间作为头结点,遇到大于或者等于x的尾插到大链表的头结点处,遇到小于x的尾插到小链表的头节点处,此时不需要单独处理第一个节点

参考答案:

不带头单向链表

/*
 * @lc app=leetcode.cn lang=c
 *
 * 面试题02.04.分割链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode SLN;
struct ListNode* partition(struct ListNode* head, int x){
    //不带头单向链表思路
    if(!head)
    {
        return NULL;
    }
    SLN* cur = head;
    SLN* lessHead = NULL, *lessTail = NULL;
    SLN* greaterHead = NULL, *greaterTail = NULL;
    //遍历数组找出小于x和大于等于x的节点分别放在小链表和大链表中
    while(cur)
    {
        SLN* next = cur->next;
        if(cur->val < x)
        {
            if(lessHead == NULL)
            {
                lessHead = lessTail = cur;
            }
            else
            {
                lessTail->next = cur;
                lessTail = lessTail->next;
            }
            lessTail->next = NULL;
        }
        else
        {
            if(greaterHead == NULL)
            {
                greaterHead = greaterTail = cur;
            }
            else
            {
                greaterTail->next = cur;
                greaterTail = greaterTail->next;
            }
            greaterTail->next = NULL;
        }
        cur = next;
    }
    //链接小链表和大链表
    if(lessTail)
    {        
        lessTail->next = greaterHead;
        return lessHead;
    }
    else
    {
        return greaterHead;
    }
}
// @lc code=end

带头单向链表

/*
 * @lc app=leetcode.cn lang=c
 *
 * 面试题02.04.分割链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode SLN;
struct ListNode* partition(struct ListNode* head, int x){
    //带头单向链表思路
    //判断链表是否为空
    if (head == NULL)
    {
        return head;
    }
    SLN* pcur = head;

    //定义小链表和大链表的指针
    SLN* lessHead = NULL;
    SLN* lessTail = NULL;
    SLN* greaterHead = NULL;
    SLN* greaterTail = NULL;

    //定义大链表和小链表的头结点
    SLN* phead = (SLN*)malloc(sizeof(SLN));
    SLN* pphead = (SLN*)malloc(sizeof(SLN));

    lessHead = lessTail = phead;
    greaterHead = greaterTail = pphead;
    //分割链表
    while (pcur)
    {
        if (pcur->val < x)
        {
            lessTail->next = pcur;
            lessTail = lessTail->next;
        }
        else
        {
            greaterTail->next = pcur;
            greaterTail = greaterTail->next;
        }
        pcur = pcur->next;
    }
    //改变大链表的最后一个节点的指针域指向空,注意此步要在合并之前,若大链表为空链表,则greaterHead->next为越界访问,提前将其置空可避免问题
    greaterTail->next = NULL;
    //合并大链表和小链表
    lessTail->next = greaterHead->next;
    SLN* ret = lessHead->next;
    free(phead);
    free(pphead);

    return ret;
}
// @lc code=end

回文链表

题目链接:234. 回文链表 - 力扣(LeetCode)

给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false

思路解析:

回文链表,本质即为以中间节点为对称轴,如果中间节点的左右部分完全相等,则证明该链表为回文链表,根据这个特点,可以考虑思路:因为回文链表以中间节点为对称轴,所以先找到中间节点,因为中间节点的左右部分完全相等,所以将中间节点的后部分链表反转再与原链表的节点中的val进行比较,如果相等即为回文链表,不相等则不为回文链表

参考答案:

/*
 * @lc app=leetcode.cn id=234 lang=c
 *
 * [234] 回文链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode SLN;

bool isPalindrome(struct ListNode *head)
{
    if (!head)
    {
        return true;
    }

    // 找出链表的中间节点
    SLN *slow = head;
    SLN *fast = head;
    SLN *prev = head;
    while (fast && fast->next)
    {
        prev = slow;
        slow = slow->next;
        fast = fast->next->next;
    }
    // 将中间节点的前一个节点置为空
    prev->next = NULL;
    // 记录中间节点的位置
    SLN *mid = slow;

    // 逆置包括中间节点在内的后面的链表
    prev = NULL;
    SLN *phead = mid;
    SLN *cur = mid;
    while (cur)
    {
        cur = cur->next;
        phead->next = prev;
        prev = phead;
        if (cur != NULL)
        {
            phead = cur;
        }
    }

    // 遍历新的链表与原链表比较,如果相同返回true,不同返回false
    cur = head;
    while (cur)
    {
        if(cur->val != phead->val)
        {
            return false;
        }
        cur = cur->next;
        phead = phead->next;
    }

    return true;
}
// @lc code=end

相交链表

题目链接:160. 相交链表 - 力扣(LeetCode)

给你两个单链表的头节点 headAheadB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null

思路解析:

本题实际上有两个问题,第一个问题:两个链表是否相交,第二个问题:两个链表相交时相交的节点是哪一个,对于第一个问题,两个链表如果相交,说明两个链表的最后一个节点一定相等(即为同一个节点,注意不是值相等,而是节点相等),通过判断两个链表的最后一个节点是否相等即可判断两个链表是否相交,对于第二个问题,如何找相交节点,回忆数学中的相遇问题,相遇的本质是走到了同一个位置,那么对于链表来说,即为走到了同一个节点,而因为两个链表相交前的节点个数是不一定相同的,所以在前面判断是否相交时,可以同时计算两个链表的长度,而如果一个链表长,那么多出来的节点个数,即为长链表中的多余部分,先让长链表遍历多出来的个数次,再同时遍历长链表和短链表,如果二者遇到了相同的节点,则说明这个节点时相交的节点

参考答案:

/*
 * @lc app=leetcode.cn id=160 lang=c
 *
 * [160] 相交链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode SLN;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
    // 因为存在不相交的情况,所以将问题分成两部分
    SLN *curA = headA;
    SLN *curB = headB;
    // 记录A链表和B链表的长度
    int lenA = 1;
    int lenB = 1;
    // 1.判断是否相交
    // 如果相交代表最后一个节点相等
    //注意使用cur->next进行判断而不是cur,如果是cur则走到结尾为空,此时curA和curB都为空,即使最后一个节点不相同也不会返回没有交点的NULL
    while (curA->next)
    {
        curA = curA->next;
        lenA++;
    }

    while (curB->next)
    {
        curB = curB->next;
        lenB++;
    }

    // 最后一个节点不同时说明两个链表没有交点
    if (curA != curB)
    {
        return NULL;
    }

    // 2.找到交点
    // 求出两个链表之间的差值
    int dis = abs(lenA - lenB);
    // 定义长链表和短链表,先假设,若不满足再修改
    SLN *longList = headA;
    SLN *shortList = headB;
    if (lenA < lenB)
    {
        longList = headB;
        shortList = headA;
    }

    //先让长的链表走差距步
    while (dis--)
    {
        longList = longList->next;
    }

    //再同时走
    while (longList != shortList)
    {
        longList = longList->next;
        shortList = shortList->next;
    }
    //走完循环则代表肯定会有交点,因为没有交点时上面的if已经判断过了
    return longList;
}
// @lc code=end

环形链表

题目链接:141. 环形链表 - 力扣(LeetCode)

给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 注意: pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false

思路解析:

本题可以使用快慢指针法,但是注意快指针每次走两步,慢指针每次走一步,当快指针和慢指针都没有进环,并且当快指针走到空时如果二者依旧没有相遇,那么说明链表没有环,而当快指针进环时,慢指针可能没有进环,此时也不可能相遇,所以当两个指针都在环内时才会相遇,而如果二者相遇,说明二者指向的节点相同,此时即有环

参考答案:

/*
 * @lc app=leetcode.cn id=141 lang=c
 *
 * [141] 环形链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode SLN;
bool hasCycle(struct ListNode *head) {
    //快慢指针法
    SLN* slow = head;
    SLN* fast = head;
    
    while(fast && fast->next)
    {
        //先让慢指针和快指针进行移动,避免出现因为都指向头结点而误以为有环
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)
        {
            //当快指针和慢指针相遇时,说明有环
            return true;
        }
    }
    //走到了尽头说明没环
    return false;

    /* 注意此题中的两个数学问题
    * 1.slow和fast为什么走两步会相遇
    * 2.如果fast走2步以上的步数是否会出现相遇,如果会什么时候相遇,什么时候不相遇
    */
}
// @lc code=end

思考问题解析:

  1. slowfast走两步会相遇是因为slowfast之间的步数差距为1(每次跳过一个节点),也就是说,两个指针的距离(单位长度)每次都会减少1,例如,假设slow指针当前在第五个节点,fast指针在第一个节点,两个指针相差4个单位长度(3个节点),当slow走一步时,slow到达第六个节点,而fast此时走两步,fast到达第三个节点,两个指针相差3个单位长度,当slow再走一步时,slow到达第七个节点,而fast此时走到第五个节点,两个指着相差2个单位长度,而当slow走到第八个节点时,此时fast走到第七个节点,两个节点相差1个单位长度,当slow走到第九个节点时,fast此时也走到第九个节点,此时二者相遇,而推广到有n个节点,每次slow走一步,fast走两步,二者之间的单位长度个数减少1,即距离减少1,最后终将会相遇
  2. 对于第二个问题,如果fast走两步以上的步数,则fastslow之间的距离差值每次减少二者之间的步数差值,如果fastslow本身相差偶数个节点,并且二者之间的步数差值为2,那么会相遇,如果fastslow相差奇数个单位长度时,此时到当slow到最后一个节点时,fastslow多走两步,走到了slow的下一个节点的下一个节点,则二者错过相遇点,同理可得更多的步数差值,而因为二者步数相差1时,每次距离减少1,无论二者之间的距离差值为奇数还是偶数都会相遇

环形链表Ⅱ

题目链接:142. 环形链表 II - 力扣(LeetCode)

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置( 索引从 0 开始)。如果 pos-1,则在该链表中没有环。 注意: pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。

思路解析:

本题可以使用快慢指针法,因为慢指针每次走一步,快指针每次走两步,所以快指针走过的路程即为慢指针的两倍,如果二者从相遇点开始起步

假设链表环前的距离为L,slow进环后的位置与环的起点的位置的距离为X,环的周长为C

那么对于慢指针来说,其走过的距离为L+X,因为快指针每次走两步,那么slowfast相遇之前,fast至少已经走了一圈,因为fastslow的前面,如果没有走一圈,不可能回到slow的位置,那么快指针走过的距离为L+nC+X(先走没有环的部分,再走一定数量的圈数,再从起点走X距离追上slow),故有等式L+X+nC = 2*(L+X),化简可得nC = L + X,故L = nC-X = (n - 1)C + C - X(先走n-1圈,再走去掉X的部分回到进环点),极限情况下,n = 1,则有L = C - X,即此时fast从相遇点开始走,走了C-X的距离刚好走到进环点,此时即为入环的第一个节点

参考答案:

/*
 * @lc app=leetcode.cn id=142 lang=c
 *
 * [142] 环形链表 II
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode SLN;
struct ListNode *detectCycle(struct ListNode *head) {
    //第一种方法:快慢指针法
    SLN* slow = head;
    SLN* fast = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)
        {
            //相等时说明相遇
            SLN* meet = slow;//保存第一次相遇点
            while (head != meet)
            {
                //一次次和相遇点比较,如果进环前的点和环中的点相等,说明是环的入口
                head = head->next;//相当于等式中L,从第一个节点开始走L个长度
                meet = meet->next;//相当于等式中的C-X,从相遇点开始走C-X个长度
            }
            return meet;
        }
    }

    //无环时返回空
    return NULL;
}
// @lc code=end

随机链表的复制

题目链接:138. 随机链表的复制 - 力扣(LeetCode)

给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。 复制链表中的指针都不应指向原链表中的节点
例如,如果原链表中有 XY 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 xy ,同样有 x.random --> y
返回复制链表的头节点。
用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:
val:一个表示 Node.val 的整数。
random_index:随机指针指向的节点索引(范围从 0n-1);如果不指向任何节点,则为 null
你的代码 接受原链表的头节点 head 作为传入参数。

思路解析:

本题可以将原链表的每一个节点都进行一次拷贝,然后插入到对应的原节点的后面,定义两个指针copyHeadcopyTail分别指向由新的节点组成的链表,根据尾插的思想一步一步先进行拷贝,再处理random,对于此题来说,当原链表节点的random为空时,拷贝链表的random也为空,当原链表的random不为空,此时观察规律可得copyTail->random = cur->random->next(例如,对于拷贝链表的第二个节点来说,其random应该指向拷贝链表的第一个节点(注意不能只比较值,因为值可能重复,但是random链接的顺序只有一个),而该第一个节点时原链表cur->random指向的第一个节点的next的节点),最后断开原链表和拷贝链表的相互链接,使二者变为两个链表,返回拷贝的链表即可

示意图如下:

参考代码:

/*
 * @lc app=leetcode.cn id=138 lang=c
 *
 * [138] 随机链表的复制
 */

// @lc code=start
/**
 * Definition for a Node.
 * struct Node {
 *     int val;
 *     struct Node *next;
 *     struct Node *random;
 * };
 */
typedef struct Node SN;
struct Node *copyRandomList(struct Node *head)
{
    SN *copyHead = NULL;
    SN *copyTail = NULL;
    SN *cur = head;
    // 1.复制原链表并插入
    while (cur)
    {
        // 复制链表
        if (copyHead == NULL)
        {
            copyHead = copyTail = (SN *)malloc(sizeof(SN));
        }
        else
        {
            copyTail = (SN *)malloc(sizeof(SN));
        }
        // 插入
        copyTail->val = cur->val;
        copyTail->next = cur->next;
        cur->next = copyTail;
        // 使指针移动
        cur = copyTail->next;
    }

    // 2.处理random
    cur = head;
    while (cur)
    {
        //因为copyTail和cur指向同一个链表,故在循环中处理copyTail的位置,因为cur会移动,所以直接使用cur->next给copyTail就可以使其移动,不需要单独用语句让copyTail移动
        copyTail = cur->next;
        // 单独处理cur->random为空的情况,防止出现空指针解引用问题
        if (cur->random == NULL)
        {
            copyTail->random = NULL;
        }
        else
        {
            copyTail->random = cur->random->next;
        }

        cur = copyTail->next;
    }

    // 3.恢复原来的链表并让新链表节点彼此连接
    cur = head;
    copyTail = copyHead;
    while (cur)
    {
        cur->next = copyTail->next;
        //注意要处理cur->next为空的问题,否则会出现空指针解引用错误
        if (cur->next != NULL)
        {
            copyTail->next = cur->next->next;
        }
        else
        {
            copyTail->next = NULL;
        }
        cur = cur->next;
        copyTail = copyTail->next;
    }
    return copyHead;
}
// @lc code=end

环形约瑟夫问题

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

编号为 1 到 n 的 n 个人围成一圈。从编号为 1 的人开始报数,报到 m 的人离开。
下一个人继续从 1 开始报数。
n-1 轮结束以后,只剩下一个人,问最后留下的这个人编号是多少?

数据范围: 1≤ n, m≤10000
进阶:空间复杂度 O(1),时间复杂度 O( n)

思路解析:

本题使用单向循环链表来实现,本题关键就是当计数器等于报数值m时,就删除节点直到最后只剩一个节点时结束

参考答案:

typedef struct ListNode STL;
//创建新节点申请空间
STL* applySpace(int x)
{
    STL* newNode = (STL*)malloc(sizeof(STL));
    if (newNode == NULL)
    {
        exit(1);
    }
    newNode->val = x;
    //将节点的next指针指向自身防止空指针
    //代替newNode->next = NULL;
    newNode->next = newNode;
    return newNode;
}
int Josephus(int n, int m) {
    int count = 1;
    //创建约瑟夫环
    STL* phead = applySpace(1);
    STL* pcur = phead;
    for (int i = 2; i <= n; i++)
    {
        pcur->next = applySpace(i);
        pcur = pcur->next;
    }
    //使链表循环
    //使prev指向最后一个节点的前一节点,防止在后续代码中出现空指针使用问题,因为当前链表是循环链表,故依旧可以遍历链表
    STL* prev = pcur;
    pcur->next = phead;

    //解决问题
    pcur = phead;
    while (pcur->next != pcur)
    {
        if (count == m)
        {
            //释放空间
            prev->next = pcur->next;
            free(pcur);
            //走到prev的next指针,此时不是pcur的next指针了
            pcur = prev->next;
            //count重置
            count = 1;
        }
        else
        {
            prev = pcur;
            count++;
            pcur = pcur->next;
        }
    }
    return pcur->val;
}
  • 30
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

怡晗★

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值