力扣每日一题__92. 反转链表 II

力扣每日一题__92. 反转链表 II

题目链接: LC 92.反转链表
在这里插入图片描述
这道题是对链表的局部进行翻转的, 我们先来看一个普通版本的链表翻转。

题目链接 : Acwing 35. 反转链表
acwing上这道题目的意思就是让你反转整个链表, 这里我们分为迭代和递归两种不同的版本

在这里插入图片描述
首先分析这个过程 , 1号节点的next指向的是2号节点, 如果我们要进行翻转链表的操作,我们应该将1号节点的next指向nullptr, 然后2号节点的next指向1号节点, 3号节点的next指向2号节点 … , 即将每一个节点的next指针指向其上一个节点。 1号节点我们只需让它指向nullptr即可。 故明白了基本的思路 ,接下来就是代码实现, 在代码实现中讨论具体的细节.

迭代版本C++的实现

struct ListNode                                                              // 节点的数据结构, 包含一个数据和指针 
{
     int val;            
     ListNode *next;
     ListNode(int x) : val(x), next(NULL) {}
 };
 

ListNode* reverseList(ListNode* head) 
    {
     // 若此时的头节点为空, 或者此时头节点的下一个为空(说明此时只有一个元素) 不用处理,直接返回头节点
        if(!head || !head->next) return head;             
        ListNode *pre = nullptr;                      
        // pre指针表示即当前节点的前一节点,由于我们的操作是将当前的节点的next更新为上一节点,
        //而第一个节点的next 应更新为nullptr, 故pre初始化为nullptr;
        auto cur = head;              // cur表示当前遍历的节点
        while(cur)                    // 遍历整个链表,更新每个节点的next的值 
        {
            auto next = cur -> next;   // 提前保存当前节点的next节点, 因为会被更新为上一节点
            cur -> next = pre;         // 将当前节点的next更新为上一节点
            pre = cur;                   // 同时要遍历下一节点时,应该同时更新pre为下一节点的上一节点,即更新为本次节点
            cur = next;                // cur更新为下一遍历节点
        }
        return pre;                  // 最后的pre为原本链表的最后一个节点,经过遍历后其成为头节点 
    }

递归版本C++的实现

我们的迭代版本是分析了翻转链表的过程就是将每个节点的next更新为上一节点这样的思路而写出的代码, 而递归版本也同样也是这个思路,只不过递归函数每次实现的是一个局部. 例如上图的例子中,当目前遍历的节点不是空且其下一个节点也不为空时,继续递归遍历,直到遇到一个节点其下一个节点为空,即当遍历到4号节点的时候发现其下一节点为空,4说明号节点已经是最后一个节点,其将作为翻转链表后的头节点,故这里直接返回4号节点, 返回调用他的上一个节点中,然后将4号节点的下一个节点更新为3号节点,即 3号节点 -> next -> next = 3号节点。 解释一下, 3号节点 -> next 就是4号节点, 然后4号节点 -> next = 3号节点, 不就是将4号节点的下一个节点更新为3号节点. 故继续返回从2号节点到3号节点之间更新, 故代码的思路为:

 ListNode* reverseList(ListNode* head) 
    {
        if(!head || !head->next) return head;             // 判断本次节点是否为空,或者是否是最后一个节点
        auto tail = reverseList(head->next);             //  递归,因为本次节点并不是最后一个节点,故继续递归直到找到最后一个节点
        
        head->next->next = head;                          // 将当前节点的下一个节点的下一个节点更新为当前节点 
        head->next = nullptr;                            // 当前节点的下一个更新为nullptr
        return tail;                                            // 返回翻转链表后的头节点 。
    }

故现在再回过头来看力扣这道题目,故这道题目在局部上翻转的策略可以采用我们的迭代版本的思路, 即将left 和 right之间的的部分进行翻转,但这里还是要注意问题,看下面图片

题目链接: 反转链表II
在这里插入图片描述

可以看到 (left + 1, right)的部分就是将每一个结点的下一个结点更新为上原本的上一节点, 但注意的是这里有两个跟全部翻转不同的地方,就是left - 1这个位置的next结点的更新和left结点的next结点的更新。而且这里我们注意到,执行局部更新头节点有可能变(即left就是头节点的时候),也可能不变(left节点在头节点右边的情况下) , 故针对这种头节点变或者不变的情况,我们可以统一用一个虚拟头节点来处理,即将虚拟头节点的next指向头节点,故不管left的位置在哪, 虚拟头节点是不会变的。故接下来看代码实现

ListNode* reverseBetween(ListNode* head, int left, int right) 
{
        ListNode* dummy = new ListNode(-1);           // 给虚拟头节点dummy申请内存
        dummy->next = head;                                    // 令虚拟头节点的next指向头节点head
        auto a = dummy;                                            // 由于left - 1这个位置的节点更新方式特殊,故我们定义一个指针遍历到 left - 1这个位置的节点
        for(int i = 0; i < left - 1; ++i) a = a->next;        // a指针遍历到 left - 1这个位置的节点
        auto b = a->next, c = b->next;                       // 此时b为left位置的节点, c为left + 1位置的节点
        for(int i = 1; i <= right - left; ++i)                    // 通过上图分析我们知道我们是从left + 1这个位置开始一直到right才是我们上面两道简单的题目的更新逻辑
        {
            auto d = c->next;                                       // 保存当前更新节点的下一节点
            c->next = b;                                               // 将当前节点的next更新为上一节点
            b = c;                                                         // 更新下一节点的上一节点是哪一个
            c = d;                                                         // 将当前节点更新为下一节点
        }
		// 最后更新两个特殊的位置 left - 1和left 
        a->next->next = c;                             // left位置的更新      
        a->next = b;                                       // left - 1位置的更新 
		// 接下来的话我们其实可以直接返回 dummy -> next; 但对于C++来说会内存泄漏故进行处理        
        auto res = dummy->next;
        delete dummy;
        return res;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值