LeetCode 链表翻转相关(24 25)

本文深入探讨链表数据结构的高效操作方法,包括两两交换节点与k个一组反转链表的实现策略。通过详细解析前插法,提供了一种理解和实践链表反转的新视角。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

解题思路

链表反转这种题,上个星期我还是不会做的,但是自从学会了前插法,写起来就游刃有余的。(说明刷题还是有进步的)
下面介绍下前插法

  1. 新建一个空节点作为反转链表的表头
  2. 将待反转链表的头结点取出,用一个指针指向头结点的下一个结点,然后将头结点指向新链表的表头
  3. 新链表的指针指向插入的头结点,待反转链表的表头为之前存放的旧结点的next结点
  4. 重复上述操作,直到反转链表全部插入到新链表中
  5. 返回新链表的头结点即可
    这样看着不好理解,直接看代码吧

24. 两两交换链表中的节点

思路

三个指针来交换

口 1 2 3 4
s  p q

口 2 1 3 4
 s q p
口 2 1 3 4
     s q p

如上 p,q指向需要交换的指针,s为p指针之前的指针(没有s会导致漏掉数)
1.p先指向q next
2.q的next为p
3.s的next为q
4.判断如果p余下的空间不够交换 就可以返回ans了
5.如果够 那么继续往下迭代
注意一开始要用ans来指向头指针 不然head和start是会改变位置的,直接返回会漏掉数
代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if(!head||!head->next) return head;
        ListNode* ptr=head;
        ListNode* qtr=head->next;
        ListNode* start=new ListNode(0);
        start->next=head;
        ListNode* ans=start;
        while(start->next)
        {
            // cout<<"start "<<start->val<<"ptr "<<ptr->val<<"qtr "<<qtr->val<<endl;
            ptr->next=qtr->next;
            qtr->next=start->next;
            start->next=qtr;
            if(ptr->next==NULL||ptr->next->next==NULL)return ans->next;
            start=ptr;
            ptr=start->next;
            qtr=ptr->next;
        }
        return ans->next;
    }
};

25.k个一组反转链表

这道题是我第一次独立做的困难题(虽然是通过率很高的困难题) 心里还是挺有成就感的,下面说一下我的思路

思路

首先我想到这题跟反转链表其实很像,就是用前插法翻转链表,只不过用的指针有点多

  1. need指向需要划分的区间的起始结点,我用一个ptr来指向need,方便划分
  2. 然后用need往下走,走到下一个区间的起始结点
  3. 现在我们有了翻转区间的起始结点和结束条件,那么我们就可以开始用前插法来翻转区间了
  4. ok,翻转完区间,我们把翻转完区间的尾结点(其实就是更改前的need哦,不用多次遍历!),指向下一个需要翻转的尾结点。这样做能使得你的链表每一次翻转都能连贯起来,并且下一次翻转不会断链,不信可以用笔验算一下。(如果碰到剩下的不用翻转怎么办?直接指向当前的need结点返回即可!)
  5. 然后我们返回链表需要注意一下,返回的是第一个区间的尾结点为头结点的链表,所以第一步要往后找到这个头结点即可。

简单的说就三步
1.划分反转区间,反转该区间
2.该区间指向下一个反转区间的尾结点(这样可以连贯的反转)
3.重复上述步骤,直到判断出剩余的结点不足够反转,就可以退出了

下面是代码,有一点长,用时16ms

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {
        
        if(head==NULL||head->next==NULL)return head;
        //题目的k有可能大于链表中的个数
        ListNode* thead=head;
        int num=0;
        while(thead)
        {
            num++;
            thead=thead->next;
        }
        if(k>num) return head;
        
        //ans为需要返回的新链表的头结点
        ListNode* ans=head;
        //need为划分区间
        ListNode* need=head;
        for(int i=0;i<k-1;i++)
        {
           if(ans) ans=ans->next;
           else return head;
        }
        bool enough=true;
        ListNode* ptr=need;
        ListNode* tneed=need;
        //insTo为翻转区域的尾节点
        ListNode* insTo;
        //ins为需要指向的下一个区间的结点
        ListNode* ins=need;
        while(need&&enough)
        {
            //指向尾结点
            insTo=need;
            //用于遍历的临时结点
            tneed=need;
            //翻转区间的起点
            ptr=need;
            //不应该在这里判断
            //用一个临时结点来判断!这一轮!是否足够翻转
            for(int i=0;i<k;i++)
            {
                if(need) need=need->next;
            }
            
            //前插法反转该链表 无论如何都需要翻转前一区间的链表
            ListNode* temp=new ListNode(0);
            while(ptr!=need)
            {
                ListNode* qtr=ptr;
                ptr=ptr->next;
                qtr->next=temp;
                temp=qtr;
            }
            
            
            //这里还需另外判断下一轮够不够!!
            tneed=need;
            for(int i=0;i<k;i++)
            {
                if(tneed) tneed=tneed->next;
                else enough=false;
            }
            //1. 下一轮没有足够的结点用于反转 直接指向need
            if(enough==false)
            {
                insTo->next=need;
            }
            else
            {
                //非空结点需要指向need的下k-1个结点
                ins=need;
                for(int i=1;i<k;i++)
                {
                    if(ins)ins=ins->next;
                }
                insTo->next=ins;
            }
        }
        return ans;
    }
};

别人的思路

看了下别人的代码。是每次都交换两个结点,而不是一次性交换所有结点,大同小异吧,只不过我用的空间更多些

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *reverseKGroup(ListNode *head, int k) {
        if (!head || k == 1) return head;
        ListNode *dummy = new ListNode(-1);
        ListNode *pre = dummy, *cur = head;
        dummy->next = head;
        int i = 0;
        while (cur) {
            ++i;
            if (i % k == 0) {
                pre = reverseOneGroup(pre, cur->next);
                cur = pre->next;
            } else {
                cur = cur->next;
            }
        }
        return dummy->next;
    }
    ListNode *reverseOneGroup(ListNode *pre, ListNode *next) {
        ListNode *last = pre->next;
        ListNode *cur = last->next;
        while(cur != next) {
            last->next = cur->next;
            cur->next = pre->next;
            pre->next = cur;
            cur = last->next;
        }
        return last;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值