【数据结构】链表OJ第二篇 —— 链表的中间节点 && 链表中倒数第k个节点 && 链表分割 && 链表的回文结构 && 相交链表

简介: 【数据结构】链表OJ第二篇 —— 链表的中间节点 && 链表中倒数第k个节点 && 链表分割 && 链表的回文结构 && 相交链表

1. 链表的中间节点


链接876. 链表的中间结点


描述:


给定一个头结点为 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,我们返回第二个结点。


提示:

   给定链表的结点数介于 1 和 100 之间。


思路1:

第一种思路是 暴力求解 。


题目不是要求链表的中间节点吗?那么我遍历链表,直接算出链表的长度。


对于奇数个节点,那么我返回 长度 / 2的节点当然没问题,且题目要求,如果有两个中间节点,则返回第二个中间节点。那不正好,我偶数个节点时 长度 / 2 就是中间第二个节点,这不就秒了吗(doge)。

c1c837cbea372a4e2a821189db450666.png


但是如果只能遍历一遍呢?我们能用什么方法解决?看思路2↓


思路2(精讲):

这里采用的思路为快慢指针。


方法是这样的,给定一个慢指针 slow 一次走一步,快指针 fast 一次走两步。快指针的速度是慢指针的2倍。


当链表的节点数为 奇数 时,快指针走到链表 最后一个节点 停止;当链表的节点数为 偶数 时。快指针走到 空指针 停止。


最后返回的 slow 就是中间节点。


这里的原理其实就是利用了一个差值的原理,当快指针走完链表,那么慢指针恰好走了它的一半,它们走的时间一样,那么慢指针就是中间节点的位置。



6e3c613d9daff7141a8d4be97abab971.png

f310f7dd701a0ca4ec074c561ae11d62.png




2. 链表中倒数第k个结点


链接链表中倒数第k个结点


描述:

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


示例1::


   输入:

   1,{1,2,3,4,5}

   返回值:

   {5}


思路1:


和求链表的中间节点的方法相似,为直接法。


要求链表的倒数第 k 个节点,那么就是删除链表正数第 len(链表长度) - k + 1 个节点。


举个例子,例如链表长度为 5,删除倒数第 2 个节点,就是删除链表正数第 4 个节点,推导出来就是第 len + 1 - k 个节点。


所以只要先算出链表长度,然后遍历到 len + 1 - k 个节点返回即可。


但是这里需要注意一下区间和迭代关系。


cf83709305749d7ab4a81b869465ece1.png



既然这道题目也可以用直接法,那么能否也适用于快慢指针?这当然可以,而且这道题的方法也很巧妙,接下来看思路2↓


思路2(精讲):

又是奇妙的快慢指针,这里大体是这样一个方案。


给定一个快指针 fast 和一个慢指针 slow。


我们要求链表倒数第 k 个节点,那么我们就先让快指针走 k 步。


然后让 fast 和 slow 一起走,当 fast 走到空指针,这时 slow 为倒数第 k 个节点。


注意:如果在 fast 走 k 步的过程中,fast 迭代为了空指针,这时直接返回空指针。


那么这里的原理是什么呢?


其实就是首先让 fast 走 k 步,让 fast 和 slow 的间隔为 k。链表的倒数第 k 个节点,就是正数 len + 1 - k 个节点,那么当我 fast 走到空指针后,链表走完,那么现在 fast 走的距离就相当于链表的长度 + 1,fast 和 slow的间隔为 k ,那么现在的 slow 就为正数 len + 1 - k个节点,这时返回 slow就是倒数第 k 个节点。



aaecfd6e038ae481714e7d2485470a94.png

6ac7e717716746a96e367d8893553dd3.png

代码

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) 
{
    struct ListNode* fast, *slow;
    fast = slow = pListHead;
    if (pListHead == NULL)
        return NULL;
    // fast 先走 k 步
    while (k--)
    {
        // 放置 fast 先走到空
        if (fast == NULL)
        {
            return NULL;
        }
        fast = fast->next;
    }
    // 迭代
    while (fast)
    {
        slow = slow->next;
        fast = fast->next;
    }
    return slow;
}



3. 链表分割


链接CM11 链表分割


描述:


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


思路:


题目要求我们将小于 x 的节点和大于等于 x 的节点分隔,小于 x 的节点在前,大于等于 x 的节点在后,且不能改变原来的数据顺序。


不能改变顺序就比较棘手,如果没有这个条件,我们可以用双指针来写。但是题目既然给出了要求,我们就得想办法解决。


我们创建一个新链表存放小于 x 的值,另一个存放大于等于 x 的值。然后遍历原链表,将符合条件的值放入对应的链表中,最后再将存放小于 x 的值的链表和存放大于等于 x 的值的链表链接起来。


那么这过程肯定是尾插,本题使用哨兵位是十分合适的,因为本题有很多的空指针处理的情况,所以我们设定两个哨兵位 lessHead 、 greaterHead。


再给定两个尾lessTail 、 greaterTail,用来尾插。 但是最后记得要释放哨兵位。


请注意,如果以 greaterHead 结束的元素不是链表的最后一个元素(即原链表最后一个元素小于 x ),就可能会造成 链表带环 的情况,需要断开环,然后将 greaterTail 的 next 置为空。同样的,过会也会画图来讲解。


38459c9d116b38fd5f6a50888eda5e84.png

c24ef860df3844195052d00bf1d03f9f.png

2f4147fe03d1695bcd86084d2383672c.png


代码

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
class Partition {
public:
    ListNode* partition(ListNode* pHead, int x) 
    {
        struct ListNode* lessTail, *lessHead, *greaterTail, *greaterHead;
        // 建立哨兵位
        lessTail = lessHead = (struct ListNode*)malloc(sizeof(struct ListNode));
        greaterTail = greaterHead = (struct ListNode*)malloc(sizeof(struct ListNode));
        struct ListNode* cur = pHead;
        while (cur)
        {
            if (cur->val < x)
            {
                lessTail->next = cur;
                lessTail = cur;
            }
            else
            {
                greaterTail->next = cur;
                greaterTail = cur;
            }
            cur = cur->next;
        }
        // 链接两个链表
        lessTail->next = greaterHead->next;
        greaterTail->next = NULL; // 断开环
    // 拷贝节点,释放哨兵位
        struct ListNode* ans = lessHead->next;
        free(lessHead);
        free(greaterHead);
        return ans;
    }
};


这道题目不用哨兵位也可以做,但是比较考验细节,思路大体差不多,有兴趣可以去试试,下面给出截图和代码:

6af92fd9fb74c7446d96baf2eb941630.png

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
class Partition {
public:
    ListNode* partition(ListNode* pHead, int x) 
    {
        struct ListNode* lessTail, *lessHead, *greaterHead, *greaterTail;
        lessTail = lessHead = greaterHead = greaterTail = NULL;
        struct ListNode* cur = pHead;
        while (cur)
        {
            if (cur->val < x)
            {
                if (lessTail == NULL)
                {
                    // 第一次尾插
                    lessHead = lessTail = cur;
                }
                else
                {
                    // 第一次尾插
                    lessTail->next = cur;
                    lessTail = lessTail->next;
                }
                cur = cur->next;
            }
            else
            {
                 if (greaterTail == NULL)
                {
                    greaterHead = greaterTail = cur;
                }
                else
                {
                    greaterTail->next = cur;
                    greaterTail = greaterTail->next;
                }
                cur = cur->next;
            }
        }
        // lessHead 为空,说明原链表为空或链表的值全大于 x
        // 且链表尾部的 next 一定为空
        // 返回 greaterHead
        if (lessHead == NULL)
            return greaterHead;
        // 如果 lessHead 和 greaterHead 都不为空
        // 说明正常分割
        // 将其链接,greaterHead 尾部链空
        if (lessHead != NULL && greaterHead != NULL)
        {
            lessTail->next = greaterHead;
            greaterTail->next = NULL;
        }
        // 无论是正常分割,还是链表的值全小于 x
        // 都是返回 lessHead
        return lessHead;
    }
};
相关文章
|
5月前
|
机器学习/深度学习 算法
24. 两两交换链表中的节点, 19.删除链表的倒数第N个节点 ,面试题 02.07. 链表相交
1. **两两交换链表中的节点**:通过引入虚拟头结点,使所有节点都能采用统一的交换逻辑,避免对头结点单独处理。 2. **删除链表的倒数第N个节点**:利用双指针技巧,让快慢指针保持N个节点的距离,当快指针到达末尾时,慢指针正好指向待删除节点的前一个节点。 3. **链表相交**:先计算两链表长度并调整起点,确保从相同距离末尾的位置开始遍历,从而高效找到相交节点或确定无交点。 以上方法均在时间复杂度和空间复杂度上进行了优化,适合用于理解和掌握链表的基本操作及常见算法设计思路。
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
522 16
|
算法
❤️算法笔记❤️-(每日一刷-160、相交链表)
❤️算法笔记❤️-(每日一刷-160、相交链表)
113 1
LeetCode第二十四题(两两交换链表中的节点)
这篇文章介绍了LeetCode第24题的解法,即如何通过使用三个指针(preNode, curNode, curNextNode)来两两交换链表中的节点,并提供了详细的代码实现。
146 0
LeetCode第二十四题(两两交换链表中的节点)
Leetcode第十九题(删除链表的倒数第N个节点)
LeetCode第19题要求删除链表的倒数第N个节点,可以通过快慢指针法在一次遍历中实现。
180 0
Leetcode第十九题(删除链表的倒数第N个节点)
(剑指offer)18、删除链表的节点—22、链表中倒数第K个节点—25、合并两个排序的链表—52、两个链表的第一个公共节点(2021.12.07)
(剑指offer)18、删除链表的节点—22、链表中倒数第K个节点—25、合并两个排序的链表—52、两个链表的第一个公共节点(2021.12.07)
160 0
探索顺序结构:栈的实现方式
探索顺序结构:栈的实现方式
169 0
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
265 59
|
5月前
|
编译器 C语言 C++
栈区的非法访问导致的死循环(x64)
这段内容主要分析了一段C语言代码在VS2022中形成死循环的原因,涉及栈区内存布局和数组越界问题。代码中`arr[15]`越界访问,修改了变量`i`的值,导致`for`循环条件始终为真,形成死循环。原因是VS2022栈区从低地址到高地址分配内存,`arr`数组与`i`相邻,`arr[15]`恰好覆盖`i`的地址。而在VS2019中,栈区先分配高地址再分配低地址,因此相同代码表现不同。这说明编译器对栈区内存分配顺序的实现差异会导致程序行为不一致,需避免数组越界以确保代码健壮性。
100 0
栈区的非法访问导致的死循环(x64)
232.用栈实现队列,225. 用队列实现栈
在232题中,通过两个栈(`stIn`和`stOut`)模拟队列的先入先出(FIFO)行为。`push`操作将元素压入`stIn`,`pop`和`peek`操作则通过将`stIn`的元素转移到`stOut`来实现队列的顺序访问。 225题则是利用单个队列(`que`)模拟栈的后入先出(LIFO)特性。通过多次调整队列头部元素的位置,确保弹出顺序符合栈的要求。`top`操作直接返回队列尾部元素,`empty`判断队列是否为空。 两题均仅使用基础数据结构操作,展示了栈与队列之间的转换逻辑。