【数据结构】链表OJ题

本文详细介绍了如何使用C语言实现链表的各种操作,包括移除链表中指定元素、反转链表、找到链表的中间节点、查找链表中倒数第k个节点、合并两个有序链表、分割链表、判断回文链表、找出链表相交节点、检测环形链表以及复制带有随机指针的链表。这些操作涉及到了链表的基本操作和高级技巧,是理解和掌握链表的重要实践。
摘要由CSDN通过智能技术生成

一、移除链表元素

题目链接

203.移除链表元素 - 力扣(LeetCode)

题目描述

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

示例 1:


输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
示例 2:

输入:head = [], val = 1
输出:[]
示例 3:

输入:head = [7,7,7,7], val = 7
输出:[]

解题思路

首先我们需要理解,移除链表中符合条件的元素和移除数组中符合条件的元素是不同的,链表中的元素是一个一个单独的节点,移除的时候需要free该节点,并且还需要重新链接链表的其他节点,所以我的思路就是遍历链表,依次访问数据,与val对比,如果不符合条件,那就把这个节点拿下来尾插到一个新的链表中,如果符合条件,就free该节点。然后将最后一个节点的next置空,防止野指针的问题。

代码实现

struct ListNode* removeElements(struct ListNode* head, int val){
    struct ListNode* newhead = NULL, *tail = NULL;
    struct ListNode* cur = head;
    while(cur)
    {
        if(cur->val != val)
        {
            //尾插
            if(tail == NULL)//放入头节点
            {
                newhead = tail = cur;
            }
            else//放入其他节点
            {
                tail->next = cur;
                tail = tail->next;
            }
            cur = cur->next;
        }
        else
        {
            struct ListNode* del = cur;
            cur = cur->next;
            free(del);
        }
    }
    //防止最后一个节点存放val元素会有野指针的问题
    if(tail)
        tail->next = NULL;
    return newhead;
}

 

二、反转链表

题目链接

206.反转链表 - 力扣(LeetCode)

题目描述

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

示例 1:

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:

输入:head = [1,2]
输出:[2,1]
示例 3:

输入:head = []
输出:[]

解题思路

遍历链表,依次取出节点尾插到新链表上即可

代码实现

struct ListNode* reverseList(struct ListNode* head){

    struct ListNode* newhead = NULL;
    struct ListNode* cur = head;
    struct ListNode* next = NULL;

    while(cur)
    {
        next = cur->next;
        cur->next = newhead;
        newhead = cur;
        cur = next;
    } 
    return newhead;
}

  

三、链表的中间节点

题目链接

876.链表的中间节点 - 力扣(LeetCode)

题目描述

给定一个头结点为 head 的非空单链表,返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

示例 1:

输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。
注意,我们返回了一个 ListNode 类型的对象 ans,这样:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.
示例 2:

输入:[1,2,3,4,5,6]
输出:此列表中的结点 4 (序列化形式:[4,5,6])
由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。

解题思路

使用快慢指针的方法,定义两个指针fast和slow,一个一次走一步,一个一次走两步,当快指针走到结尾的时候,慢指针正好走到中间节点。

代码实现

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

  

四、链表中倒数第k个结点

题目链接

题目链接(来源:牛客网)

题目描述

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

示例1:

输入: 1,{1,2,3,4,5}
返回值: {5}

解题思路

快慢指针解法,快指针比慢指针先走k步,当快指针走到终点的时候,慢指针刚好是倒数第k个节点。

代码实现

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
    // write code here
    struct ListNode* slow, *fast;
    slow = fast = pListHead;
    int i = 0;
    for(i = 0; i < k && fast != NULL; i++)//快指针先走k步
    {
        fast = fast->next;
    }
    if(i < k)//链表长度小于k的情况
    {
        return NULL;
    }
    while(fast != NULL)//迭代器
    {
        slow = slow->next;
        fast = fast->next;
    }
    
    return slow;
}

 

五、合并两个有序链表

题目链接

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

题目描述

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

示例 1:


输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
示例 2:

输入:l1 = [], l2 = []
输出:[]
示例 3:

输入:l1 = [], l2 = [0]
输出:[0]

解题思路

整体思路和合并两个有序数组基本相同,就是分别遍历两个链表,比较val的值,取较小的尾插,但是,我们在比较之前无法得知头节点是哪一个,所以,这里我的解决方案是使用一个哨兵位的头节点guard,然后遍历两个链表,当其中一个链表结束时,结束循环,然后判断时哪个链表结束了,将另一个链表链接在新链表的后面,然后记录下guard->next,然后free guard,返回guard->next。

代码实现

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){

    struct ListNode* guard = (struct ListNode*)malloc(sizeof(struct ListNode));//创建哨兵位的头节点
    guard->next = NULL;
    struct ListNode* tail = guard;
    struct ListNode* cur1 = list1;
    struct ListNode* cur2 = list2;

    while(cur1 && cur2)//取较小的节点尾插
    {
        if(cur1->val < cur2->val)
        {
            tail->next = cur1;
            cur1 = cur1->next;
        }
        else
        {
            tail->next = cur2;
            cur2 = cur2->next;
        }
        tail = tail->next;
    }
    //判断哪个链表先结束
    if(cur1)
        tail->next = cur1;
    if(cur2)
        tail->next = cur2;
    list1 = guard->next;
    free(guard);
    return list1;
}

 

六、链表分割

题目链接

题目链接(来源:牛客网)

题目描述

现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。

解题思路

由于我们不能改变原来的数据顺序,那么就只能使用两个链表,分别按顺序尾插大于x的值与小于x的值,直到原链表结束,然后将两个新链表链接起来,尾节点的next置空。

代码实现

class Partition {
public:
    ListNode* partition(ListNode* pHead, int x) {
        // write code here
        //创建两个guard节点
        struct ListNode* lessGuard, *lessTail, *greaterGuard, *greaterTail;
        lessGuard = lessTail = (struct ListNode*)malloc(sizeof(struct ListNode));
        greaterGuard = greaterTail = (struct ListNode*)malloc(sizeof(struct ListNode));
        lessGuard->next = NULL;
        greaterGuard->next = NULL;
        struct ListNode* cur = pHead;
        
        while(cur)
        {
            if(cur->val < x)//小于x的节点
            {
                lessTail->next = cur;
                lessTail = cur;
            }
            else
            {
                greaterTail->next = cur;//大于等于x的节点
                greaterTail = cur;
            }
            cur = cur->next;//迭代
        }

        lessTail->next = greaterGuard->next;//将两个链表链接
        greaterTail->next = NULL;
        
        pHead = lessGuard->next;
        free(lessGuard);
        free(greaterGuard);
        
        return pHead;
    }
};

七、链表的回文结构

题目链接

题目链接(来源:牛客网)

题目描述

对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。

测试样例:

1->2->2->1

返回:true

解题思路

根据回文链表的结构,我们找到链表的终点,然后逆置后半段链表,与前半段比较,如果重合的话,证明原链表就是回文链表,否则就不是回文链表,为了严谨,最后我们应该把原链表还原。  

代码实现

class PalindromeList {
public:
    struct ListNode* reverseList(struct ListNode* head){//逆置链表
        struct ListNode* newhead = NULL;
        struct ListNode* next = NULL;
        struct ListNode* cur = head;
        while(cur)
        {
            next = cur->next;
            cur->next = newhead;
            newhead = cur;
            cur = next;
        }
        return newhead;
    }
    struct ListNode* middleNode(struct ListNode* head){//找到中间结点
        struct ListNode* slow, *fast;
        fast = slow = head;
        while(fast && fast->next)
        {
            slow = slow->next;
            fast = fast->next->next;
        }
        return slow;
    }
    bool chkPalindrome(ListNode* A) {
        // write code here
        struct ListNode* mid = middleNode(A);//找到中间节点
        struct ListNode* r_mid = reverseList(mid);//逆置链表后半部分
        while(A && r_mid)
        {
            if(A->val != r_mid->val)
                return false;
            A = A->next;
            r_mid = r_mid->next;
        }
        return true;
    }
};

八、相交链表

题目链接

160.相交链表 - 力扣(LeetCode)

题目描述

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。图示两个链表在节点 c1 开始相交:

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构

示例 1:

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
示例 2:

输入:intersectVal = 2, listA = [1,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at '2'
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [1,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例 3:

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。这两个链表不相交,因此返回 null 。

解题思路

  根据相交链表的结构性质,我们可以知道,如果两个链表相交,那么最坏的可能就是两个链表只有最后一个节点是相交的,也就是“同一个”节点,而且我们不知道两个链表在相交之前分别由多少个节点,那么可以通过比较两个链表的长度来算,链表长度之差就是长链表在相交节点之前比锻炼表多的节点个数。然后让长链表先走相差个数步,然后两个链表同时走,每走一步比较一下节点是否相同,直到节点相同跳出循环,或者任意一个链表结束结束循环返回空指针。

代码实现

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    if(headA == NULL || headB == NULL)
        return NULL;
    struct ListNode* curA = headA, *curB = headB;
    int lenA = 1, lenB = 1;
    while(curA->next)//遍历链表,找尾结点,记录链表长度
    {
        curA = curA->next;
        ++lenA;
    }
    while(curB->next)
    {
        curB = curB->next;
        ++lenB;
    }

    if(curA != curB)//判断是否相交
        return NULL;
    
    struct ListNode* longList = headA, *shortList = headB;
    if(lenA < lenB)
    {
        longList = headB;
        shortList = headA;
    }
    //长的链表先走差距步
    int gap = abs(lenA-lenB);
    while(gap--)
    {
        longList = longList->next;
    }

    while(longList != shortList)
    {
        longList = longList->next;
        shortList = shortList->next;
    }

    return longList;
}

九、环形链表

题目链接

141.环形链表 - 力扣(LeetCode)

题目描述

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

示例 1:

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:

输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:

输入:head = [1], pos = -1
输出:false
解释:链表中没有环。

解题思路

定义快慢指针,快指针一次走两步,慢指针一次走一步,如果链表有环,当快慢指针都进环以后,快指针会追逐慢指针,直到两个指针相遇,return true,否则快指针先遍历完节点,结束循环,return false。  

代码实现

bool hasCycle(struct ListNode *head) {
    struct ListNode* slow,*fast;
    slow = fast = head;
    while(fast && fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;
        
        if(fast == slow)
            return true;
    }
    return false;
}

  

十、环形链表2

题目链接

142.环形链表 2 - 力扣(LeetCode)

题目描述

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

示例 1:

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:

输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:

输入:head = [1], pos = -1
输出:false
解释:链表中没有环。

解题思路

思路一:

 L表示进环之前的长度,C表示环的长度,所以慢指针走的长度是L+X,假设快指针在环内绕了n圈以后与慢指针相遇,所以快指针走的长度是L+nC+x,由于快指针的速度是慢指针的二倍,所以2*(L+X) = L+nC+X,化简得:L = nC-X,所以一个指针从相遇点开始走,另一个指针从链表头开始走,两个指针会在进环点相遇。所以当两个指针相遇时,return该指针即可。

代码实现

struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode* slow,*fast;
    fast = slow = head;
    while(fast && fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;
        if(fast == slow)
        {
            struct ListNode* meet = fast;
            struct ListNode* cur = head;
            while(cur != meet)
            {
                cur = cur->next;
                meet = meet->next;
            }
            return meet;
        }
    }
    
    return NULL;
}

思路二:

 

可以把相遇点和原来的头看成是两个链表的的头,然后进环点看成是两个链表的相交点,找到相交点返回即可。找相交链表的相交点的问题在上面我们已经实现过了。但是由于原链表有环,所以应该把相遇点meet的next置空,在实现操作之后恢复链表。

代码实现

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    if(headA == NULL || headB == NULL)
        return NULL;
    struct ListNode* curA = headA, *curB = headB;
    int lenA = 1, lenB = 1;
    while(curA->next)//遍历链表,找尾结点
    {
        curA = curA->next;
        ++lenA;
    }
    while(curB->next)
    {
        curB = curB->next;
        ++lenB;
    }

    if(curA != curB)//判断是否相交
        return NULL;
    
    struct ListNode* longList = headA, *shortList = headB;
    if(lenA < lenB)
    {
        longList = headB;
        shortList = headA;
    }
    //长的链表先走差距步
    int gap = abs(lenA-lenB);
    while(gap--)
    {
        longList = longList->next;
    }

    while(longList != shortList)
    {
        longList = longList->next;
        shortList = shortList->next;
    }

    return longList;
}


struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode* slow,*fast;
    fast = slow = head;
    while(fast && fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;
        if(fast == slow)
        {
            struct ListNode* meet = slow;
            struct ListNode* next = meet->next;
            meet->next = NULL;

            struct ListNode* entryNode = getIntersectionNode(head,next);
            meet->next = next;
            return entryNode;
        }
    }
    
    return NULL;
}

 

 

十一、复制带随机指针的链表

题目链接

138.复制带随机指针的链表 - 力扣(LeetCode)

题目描述

给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。

例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。返回复制链表的头节点。用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:

  • val:一个表示 Node.val 的整数。
  • random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为  null 。

你的代码 只 接受原链表的头节点 head 作为传入参数。

示例 1:

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
示例 2:

输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]
示例 3:

输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]

解题思路

在拷贝节点的时候,对于val,直接复制即可,但是其中的指针不能够直接复制,因为指针存放的是节点的地址,拷贝的新节点和原节点的地址是不同的。在这里拷贝的时候我们应该遍历链表,每遇到一个节点,就应该拷贝一次,并且将拷贝的节点链接在原节点的后面,然后再给random赋值为原节点的next,再将拷贝节点取下来重新链接成为新节点并且恢复原链表。

代码实现

struct Node* copyRandomList(struct Node* head) {
	struct Node* cur = head;
    struct Node* copy = NULL;
    struct Node* next = NULL;
    //复制节点,链接在原节点的后面
    while(cur)
    {
        //复制链接
        next = cur->next;
        copy = (struct Node*)malloc(sizeof(struct Node));
        copy->val = cur->val;

        cur->next = copy;
        copy->next = next;
        //迭代
        cur = next;
    }
    //更新random
    cur = head;
    while(cur)
    {
        copy = cur->next;

        if(cur->random == NULL)
            copy->random = NULL;
        else
            copy->random = cur->random->next;

        cur = cur->next->next;
    }
    //解下来copy节点,回复原链表
    cur = head;
    struct Node* copyHead;
    struct Node* copyTail;
    copyHead = copyTail = NULL;
    while(cur)
    {
        copy = cur->next;
        next = copy->next;
        //取节点尾插
        if(copyTail == NULL)
            copyHead = copyTail = copy;
        else
        {
            copyTail->next = copy;
            copyTail = copyTail->next;
        }
        //恢复原链接
        cur->next = next;
        //迭代器
        cur = next;
    }
    return copyHead;

}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

凌云志.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值