数据结构(初阶)笔记归纳7:链表OJ

链表OJ

目录

链表OJ

试题1:返回倒数第k个节点

试题2:链表的回文结构

试题3:相交链表

试题4:环形链表

试题5:环形链表II

试题6:随机链表的复制

七、总结


试题1:返回倒数第k个节点

题目内容:

实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值

示例:

输入:1->2->3->4->5 和 k = 2

输出:4

思路1:快慢指针法

思路解析:

创建一个慢指针slow,快指针fast

快指针先走k步,然后快慢指针同时走

快指针指向空后结束,此时slow指向倒数第k个节点

代码部分:

cpp 复制代码
/*
Definition for singly-linked list.
struct ListNode 
{
    int val;
    struct ListNode *next;
};
*/
int kthToLast(struct ListNode* head, int k) 
{
    struct ListNode* fast = head,*slow = head;
    /*快指针先走k步*/
    while(k--)
    {
        fast = fast->next;
    }
    /*快慢指针同时走*/
    while(fast)
    {
        slow = slow->next;
        fast = fast->next;
    }
    return slow->val;
}

试题2:链表的回文结构

题目内容:

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

示例:

输入:1->2->2->1

输出:true

思路1:逆置法

思路解析:

查找中间节点,逆置后半段节点,将头节点与中间节点往后的数据依次进行比较

相同继续往后走,不同返回false,直到有一个指向空后结束循环,返回true

代码部分:

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

struct ListNode* reverseList(struct ListNode* head) 
{
    if(head == NULL)
    {
        return head;
    }
    ListNode* n1,*n2,*n3;
    n1 = NULL;n2 = head;n3 = n2->next;
    while(n2)
    {
        n2->next = n1;
        n1 = n2;
        n2 = n3;
        if(n3)
        {
            n3 = n3->next;
        }
    }
    return n1;
}

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

bool chkPalindrome(ListNode* A) 
{
    /*查找中间节点*/
    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;
}
};

试题3:相交链表

题目内容:

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

示例:

输入:intersectVal = 8,listA = [4,1,8,4,5],listB = [5,6,1,8,4,5],skipA = 2,skipB = 3

输出:Intersected at '8'

思路解析:

判断相交:如果A链表和B链表的尾节点地址相同则相交

寻找交点:

  • 法1:A链表的每个节点依次跟B链表的所有节点比较,当A链表某个节点地址和B链表某个节点地址相等,这个节点就是交点,这种方法的时间复杂度为O(N^2)
  • 法2:先算出两个链表节点个数差,让长链表先走差距步,然后两个链表同时走,第一个相等的就是节点,这种方法的时间复杂度为O(N),相比法1效率更高

代码部分:

cpp 复制代码
/*
Definition for singly-linked list.
struct ListNode {
    int val;
    struct ListNode *next;
};
*/
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) 
{
    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;
    }

    /*计算两个链表节点个数差*/
    int gap = abs(lenA - lenB);
    /*假设法,先假设A长,B短*/
    struct ListNode* longList = headA,*shortList = headB;
    /*如果假设错误,则A短,B长*/
    if(lenB > lenA)
    {
        longList = headB;
        shortList = headA;
    }
    /*长的先走差距步*/
    while(gap--)
    {
        longList = longList->next;
    }
    /*同时往后走*/
    while(longList != shortList)
    {
        longList = longList->next;
        shortList = shortList->next;
    }

    return shortList;
}

试题4:环形链表

题目内容:

给你一个链表的头节点head,判断链表中是否有环

如果链表中有某个节点,可以通过连续跟踪next指针再次到达,则链表中存在环

为了表示给定链表中的环,评测系统内部使用整数pos来表示链表尾连接到链表中的位置(索引从0开始)注意:pos不作为参数进行传递 ,仅仅是为了标识链表的实际情况

如果链表中存在环 ,则返回true,否则返回false

示例:

输入:head = [3,2,0,-4],pos = 1

输出:true

思路1:快慢指针法

思路解析:

创建一个快指针fast,慢指针slow

慢指针走一步,快指针走两步

fast先进入环形链表,slow后进入环形链表

若快指针与慢指针相遇,则说明链表成环

代码部分:

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

        if(fast == slow)
        {
            return true;
        }
    }
    return false;
}

**问题1:**证明当慢指针走一步,快指针走两步时两个指针一定相遇?

假设慢指针进环时,快慢指针距离为n,每追击一次,距离缩小1,当距离为0时相遇

**问题2:**证明当慢指针走一步,快指针走三步时是否会相遇?

假设进环前长度为L,环长为C,慢指针进环时,距离差为N

快指针走的距离:L + x*C + C - N

慢指针走的距离:L

由于:3 * slow = fast

所以:3L = L + x*C + C - N

2L = ( x + 1 ) * C - N

左边的式子一定为偶数

当C为偶数,N为奇数时:

偶数 * 任何数 == 偶数:(x+1)*c为偶数

偶数 - 奇数 == 奇数:右边结果为奇数

所以左边为偶数,右边是奇数,一定追不上

当C为奇数,N为偶数时

若x+1为偶数 偶数 * 奇数 == 偶数 偶数 - 偶数 == 偶数

若x+1为奇数 奇数 * 奇数 == 奇数 奇数 - 偶数 == 奇数

所以当x+1为偶数时,左右为偶数,可能追上

当C为偶数,N为偶数时

(x+1)*c为偶数 偶数 - 偶数 == 偶数

所以左边和右边都为偶数,一定能追得上

当C为奇数,N为奇数时

若x+1为奇数 奇数 * 奇数 == 奇数 奇数 - 奇数 = 偶数

若x+1为偶数 偶数 * 奇数 == 偶数 偶数 - 奇数 = 奇数

所以当x+1为奇数时,左右为偶数,可能追上

拓展:

若速度差与环长互质,就一定能够相遇

若不互质,只有当初始距离为它们最大公约数的倍数时,才能相遇

试题5:环形链表II

题目内容:

给定一个链表的头节点head,返回链表开始入环的第一个节点,如果链表无环,则返回NULL,如果链表中有某个节点,可以通过连续跟踪next指针再次到达,则链表中存在环,为了表示给定链表中的环,评测系统内部使用整数pos来表示链表尾连接到链表中的位置(索引从0开始)如果pos是-1,则在该链表中没有环

示例:

输入:head = [3,2,0,-4],pos = 1

输出:返回索引为1的链表节点

注意:

pos不作为参数进行传递,仅仅是为了标识链表的实际情况,不允许修改链表

思路1:快慢指针法

思路解析:

创建一个快指针fast,慢指针slow

慢指针走一步,快指针走两步

当快慢指针相遇时,创建meet指针指向相遇的位置

meet与head同时走,每次走一步

当meet与head相遇时就是入环节点

代码部分:

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

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

**问题1:**为什么meet与head相遇位置就是入环节点位置?

假设进环前长度为L,环长为C,慢指针进环时,距离差为N

快指针每次走两步,慢指针每次走一步

相遇时,slow走的路程为L+N,fast走的路程为L + x*C + N

由于:2 * slow = fast

所以:2*(L + N) = L + x*C + N

L = x*C - N等价于L = C - N

即从链表头走到环入口的距离大于从相遇点走到环入口的距离

思路2:断环法

思路解析:

创建临时变量newhead存放相遇节点meet的下一个节点

将相遇节点meet的指针域置为NULL,断开环形链表

从初始节点与下部分断环处节点开始

寻找进环链表,与下断环链表的交点

代码部分:

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

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) 
{
    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;
    }

    int gap = abs(lenA - lenB);
    struct ListNode* longList = headA,*shortList = headB;
    if(lenB > lenA)
    {
        longList = headB;
        shortList = headA;
    }
    while(gap--)
    {
        longList = longList->next;
    }
    while(longList != shortList)
    {
        longList = longList->next;
        shortList = shortList->next;
    }

    return shortList;
}

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

        if(slow == fast)
        {
            struct ListNode* meet = slow;
            struct ListNode* newhead = meet->next;
            meet->next = NULL;
            return getIntersectionNode(head,newhead);
        }
    }
    return NULL;
}

试题6:随机链表的复制

题目内容:

给你一个长度为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作为传入参数

示例:

输入:head = [[7,NULL],[13,0],[11,4],[10,2],[1,0]]

输出: [[7,NULL],[13,0],[11,4],[10,2],[1,0]]

思路1:拷贝法

思路解析:

把拷贝节点插入在原节点的后面

拷贝节点的数值为当前节点的数值

拷贝节点的next指针变量存放当前节点下一个节点的地址

拷贝节点的random指针变量存放当前节点random指针指向节点的地址

把拷贝节点取下来尾插成为新链表,恢复原链表

代码部分:

cpp 复制代码
/*
Definition for a Node.
struct Node 
{
    int val;
    struct Node *next;
    struct Node *random;
};
*/

struct Node* copyRandomList(struct Node* head) 
{
	struct Node* cur = head;
    //拷贝节点插入原节点后面
    while(cur)
    {
        struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
        copy->val = cur->val;
        copy->next = cur->next;
        cur->next = copy;
        cur = copy->next;
    }

    //控制random
    cur = head;
    while(cur)
    {
        struct Node* copy = cur->next;
        if(cur->random == NULL)
        {
            copy->random = NULL;
        }
        else
        {
            copy->random = cur->random->next;
        }
        cur = copy->next;
    }

    //把拷贝节点取下来尾插成为新链表,然后恢复原链表
    struct Node* copyhead = NULL,*copytail = NULL;
    cur = head;
    
    while(cur)
    {
        struct Node* copy = cur->next;
        struct Node* next = copy->next;

        if(copytail == NULL)
        {
            copyhead = copytail = copy;
        }
        else
        {
            copytail->next = copy;
            copytail = copytail->next;
        }

        cur->next = next;
        cur = next;
    }
    return copyhead;
}

七、总结

本篇博客是对于数据结构中链表OJ的整理归纳,后续还会更新栈和队列等内容,如果对你有帮助,欢迎点赞+收藏+关注,让我们一起共同进步🌟~

相关推荐
tobias.b2 小时前
408真题解析-2010-3-数据结构-线索二叉树
数据结构·链表·计算机考研·408真题解析
tobias.b2 小时前
408真题解析-2010-2-数据结构-双端队列
数据结构·计算机考研·408真题解析
旭意2 小时前
数据结构-红黑树和set
数据结构·c++·算法·蓝桥杯
充值修改昵称2 小时前
数据结构基础:堆高效数据结构全面解析
数据结构·python·算法
2501_901147832 小时前
组合总和IV——动态规划与高性能优化学习笔记
学习·算法·面试·职场和发展·性能优化·动态规划·求职招聘
好奇龙猫2 小时前
【大学院-筆記試験練習:线性代数和数据结构(15)】
数据结构·线性代数
无心水2 小时前
8、吃透Go语言container包:链表(List)与环(Ring)的核心原理+避坑指南
java·开发语言·链表·微服务·架构·golang·list
人工智能培训2 小时前
数字孪生技术:工程应用图景与效益评估
人工智能·python·算法·大模型应用工程师·大模型工程师证书
源代码•宸2 小时前
Golang原理剖析(Go语言垃圾回收GC)
经验分享·后端·算法·面试·golang·stw·三色标记