数据结构之线性表——LeetCode:234. 回文链表,143. 重排链表,25. K 个一组翻转链表

234. 回文链表

题目描述

234. 回文链表

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

运行代码

class Solution {
    ListNode* frontPointer;
public:
    bool recursivelyCheck(ListNode* currentNode) {
        if (currentNode != nullptr) {
            if (!recursivelyCheck(currentNode->next)) {
                return false;
            }
            if (currentNode->val != frontPointer->val) {
                return false;
            }
            frontPointer = frontPointer->next;
        }
        return true;
    }

    bool isPalindrome(ListNode* head) {
        frontPointer = head;
        return recursivelyCheck(head);
    }
};

代码思路

  1. recursivelyCheck 函数

    • 这个函数是递归函数,用于逐个检查链表节点的值是否构成回文。
    • 如果当前节点 currentNode 不为空:
      • 首先递归调用自身,传入当前节点的下一个节点 currentNode->next。如果递归返回 false,说明后续的节点不满足回文条件,直接返回 false
      • 接着,比较当前节点的值 currentNode->val 和全局变量 frontPointer 指向节点的值。如果不相等,说明不满足回文条件,返回 false
      • 最后,将 frontPointer 向后移动一位,即 frontPointer = frontPointer->next
    • 如果当前节点为空,说明已经遍历完链表的一半(对于奇数长度链表,中间节点不参与比较),返回 true
  2. isPalindrome 函数

    • 这个函数是对外提供的接口函数。
    • 它首先将全局变量 frontPointer 初始化为传入的链表头节点 head
    • 然后调用 recursivelyCheck 函数,传入链表头节点 head 开始递归检查。

该算法的核心思想是通过递归的方式,从链表的末尾开始向前比较节点的值。在递归的过程中,一边向后遍历链表,一边与从链表头部开始的节点值进行比较。如果在任何一个位置发现不相等,就说明链表不是回文链表。当递归结束时,如果没有发现不相等的情况,就说明链表是回文链表。

143. 重排链表

题目描述

143. 重排链表

给定一个单链表 L 的头节点 head ,单链表 L 表示为:L0 → L1 → … → Ln - 1 → Ln

请将其重新排列后变为:L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

运行代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* reverse(ListNode* head) {
        if (head == nullptr || head->next == nullptr)
            return head;
        ListNode* newhead = reverse(head->next);
        head->next->next = head;
        head->next = nullptr;
        return newhead;
    }

    void reorderList(ListNode* head) {
        ListNode *slow = head, *fast = head;

        while (fast->next != nullptr && fast->next->next != nullptr) {
            slow = slow->next;
            fast = fast->next->next;
        }
        slow = reverse(slow->next);
        fast = head;
        while (fast != nullptr && slow != nullptr) {
            ListNode* temp1 = fast->next;
            ListNode* temp2 = slow->next;
            fast->next = slow;
            slow->next = temp1;
            slow = temp2;
            fast = fast->next->next;
        }
        if (fast != nullptr)
            fast->next = nullptr;
    }
};

代码思路

一、整体方法概述

这段代码的目的是对给定的单链表进行重排,使其满足特定的顺序要求。主要通过找到链表的中间节点,将后半部分链表反转,然后交替合并前半部分和反转后的后半部分来实现重排。

二、函数分析

  1. reverse 函数

    • 这个函数用于反转单链表。
    • 首先判断链表为空或者只有一个节点的情况,直接返回该链表。
    • 然后递归调用自身,传入当前节点的下一个节点,获取反转后的链表头节点 newhead
    • 接着,将当前节点的下一个节点的 next 指针指向当前节点,实现反转。
    • 最后,将当前节点的 next 指针置为 nullptr,返回反转后的链表头节点。
  2. reorderList 函数

    • 首先,使用快慢指针法找到链表的中间节点。慢指针 slow 每次移动一步,快指针 fast 每次移动两步。当快指针到达链表末尾或者下一个节点为 nullptr 时,慢指针就位于链表的中间位置。
    • 接着,将慢指针的下一个节点作为后半部分链表的头节点,调用 reverse 函数将后半部分链表反转,得到反转后的链表头节点 slow
    • 然后,将前半部分链表的头节点 fast 和反转后的后半部分链表的头节点 slow 进行交替合并。
    • 在合并过程中,使用临时变量 temp1 和 temp2 分别保存当前前半部分节点的下一个节点和当前后半部分节点的下一个节点。
    • 将当前前半部分节点的 next 指针指向当前后半部分节点,将当前后半部分节点的 next 指针指向保存的前半部分节点的下一个节点。
    • 然后移动前半部分和后半部分的指针到下一个位置,继续进行合并操作。
    • 最后,如果前半部分链表还有剩余节点,将其 next 指针置为 nullptr

三、算法思路总结

该算法首先找到链表的中间节点,将链表分为前半部分和后半部分。然后反转后半部分链表,最后交替合并前半部分和反转后的后半部分链表,实现了对单链表的重排操作。整个过程通过巧妙地运用指针操作和递归实现了链表的反转和合并,时间复杂度为O(n) ,其中  是链表的长度,因为需要遍历链表两次(一次找到中间节点,一次进行合并)。空间复杂度主要取决于递归调用栈的空间,在最坏情况下为O(n/2) ,因为递归反转后半部分链表时,递归栈的深度最多为链表长度的一半。

25. K 个一组翻转链表

题目描述

25. K 个一组翻转链表

给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

运行代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {
        int n=0;
        for(ListNode *cur=head;cur;cur=cur->next)
        ++n;
        ListNode *dummy=new ListNode(0,head),*p0=dummy;
        ListNode *pre=nullptr,*cur=head;
        for(;n>=k;n-=k){
            for(int i=0;i<k;++i){
                ListNode *nxt=cur->next;
                cur->next=pre;
                pre=cur;
                cur=nxt;
            }
            ListNode *nxt=p0->next;
            p0->next->next=cur;
            p0->next=pre;
            p0=nxt;
        }
        return dummy->next;

    }
};

代码思路

一、整体方法概述

这段代码的目的是将给定的链表按照每 k 个节点一组进行翻转。如果节点总数不是 k 的整数倍,则将最后剩余的节点保持原有顺序。

二、函数分析

  1. 首先,通过遍历链表计算链表的节点总数 n
  2. 创建一个虚拟头节点 dummy,并将其 next 指针指向原始链表的头节点 head。同时,定义指针 p0 指向 dummy,用于在后续的操作中追踪每一组的前一个节点。
  3. 定义指针 pre 和 cur,分别初始化为 nullptr 和 head,用于在翻转每一组节点时使用。
  4. 进入一个循环,只要剩余节点数量 n 大于等于 k,就继续进行分组翻转操作。
  5. 在每一组的翻转操作中,进行 k 次循环。在每次循环中:
    • 更新 pre 为当前节点 curcur 为保存的下一个节点 nxt
    • 然后,将当前节点 cur 的 next 指针指向前一个节点 pre,实现翻转操作。
    • 首先,保存当前节点 cur 的下一个节点为 nxt
  6. 当一组节点翻转完成后,进行以下操作:
    • 保存 p0 的下一个节点为 nxt
    • 将 p0 的下一个节点(即当前组的第一个节点)的 next 指针指向剩余未翻转的部分(即 cur)。
    • 将 p0 的 next 指针指向翻转后的组的新头节点(即 pre)。
    • 更新 p0 为 nxt,准备处理下一组节点。
  7. 循环结束后,返回虚拟头节点 dummy 的下一个节点,即为翻转后的链表的头节点。

三、算法思路总结

该算法通过计算链表长度确定需要翻转的组数,然后对每一组节点进行翻转操作。在翻转过程中,使用临时指针保存节点的关系,确保正确地翻转每一组节点,并将翻转后的组连接到正确的位置。最后,通过虚拟头节点返回翻转后的链表。整个过程的时间复杂度为O(n) ,其中  是链表的长度,因为需要遍历链表两次(一次计算长度,一次进行翻转操作)。空间复杂度为O(1) ,因为只使用了有限的额外指针变量,不随输入规模增长。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

筱姌

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

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

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

打赏作者

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

抵扣说明:

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

余额充值