LeetCode 206. 反转链表 & 92. 反转链表 II(玩出花的递归)

2020年7月23日 周四 天气晴 【不悲叹过去,不荒废现在,不惧怕未来】


本文目录

    • 1. 反转链表(LeetCode 206)
      • 解法1. 迭代
      • 解法2. 递归
    • 2. 反转链表 II(LeetCode 92)
      • 解法1. 递归
        • 2.1.1 反转链表前 N 个结点
        • 2.1.2 反转链表的一部分
      • 解法2. 迭代
    • 参考文献


1. 反转链表(LeetCode 206)

LeetCode 206. 反转链表,这道题一般有两种解法:迭代和递归,我觉得两种方法都特别经典,最好都能掌握。
LeetCode 206. 反转链表 & 92. 反转链表 II(玩出花的递归)_第1张图片

解法1. 迭代

迭代一次的具体过程:

  1. 建立两个指针 pre = NULL,cur = head
  2. 记录 cur 的下个结点为 tmp
  3. 将 cur 反向指向 pre
  4. pre 和 cur 均前进一步(即:pre = cur; cur = tmp)

这样便完成了一次局部反转,重复这个过程,直到 cur 指向NULL,返回当前的 pre ,即为所求。

图示如下:
LeetCode 206. 反转链表 & 92. 反转链表 II(玩出花的递归)_第2张图片

代码如下:

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode *pre = NULL, *cur = head;
        while(cur){
            ListNode *tmp = cur->next;
            curr->next = pre;
            pre = cur;
            cur = tmp;
        }
        return pre;
    }
};

解法2. 递归

递归的思想稍微复杂一点,这里会大量借鉴算法大佬 labuladong 的文章来说(十分感谢 labuladong 大佬),先给上递归的代码:

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
    	// base case
        if(head==NULL || head->next==NULL)
            return head;
        ListNode* last= reverseList(head->next);
        head->next->next = head;
        head->next = NULL;
        return last;
    }
};

对于递归算法,最重要的就是明确递归函数的定义。具体来说,reverseList 函数定义是这样的:

输入一个结点 head,将「以 head 为起点」的链表反转,并返回反转之后的头结点。

明白了函数的定义,在来看这个问题。比如说我们想反转这个链表:
LeetCode 206. 反转链表 & 92. 反转链表 II(玩出花的递归)_第3张图片

会在这行代码进行递归:

ListNode* res = reverseList(head->next);

不要跳进递归(按照 labuladong 大佬的话说:你的脑袋能压几个栈呀?),而是要根据刚才的函数定义,来弄清楚这段代码会产生什么结果:
LeetCode 206. 反转链表 & 92. 反转链表 II(玩出花的递归)_第4张图片

reverseList(head->next) 执行完成后,整个链表就成了这样:
LeetCode 206. 反转链表 & 92. 反转链表 II(玩出花的递归)_第5张图片

并且根据函数定义,reverseList 函数会返回反转之后的头结点,我们用变量 last 来接收。

现在我们要考虑的就是:“我子结点下的所有结点都已经反转好了,现在就剩我和我的子结点没有完成最后的反转了,所以反转一下我和我的子结点。”

来看下面这行代码:

head->next->next = head;

接下来:

head->next = NULL;
return last;

LeetCode 206. 反转链表 & 92. 反转链表 II(玩出花的递归)_第6张图片

这样整个链表就反转过来了!

我们不需要去考虑递归中间繁杂的过程,只要处理好最后面(或最前面)一小步,中间的过程只不过是在循环进行这一小步而已,这就是分治的思想。(秒,实在是妙~)


2. 反转链表 II(LeetCode 92)

LeetCode 92. 反转链表 II
LeetCode 206. 反转链表 & 92. 反转链表 II(玩出花的递归)_第7张图片

解法1. 递归

2.1.1 反转链表前 N 个结点

在实现反转链表的一部分之前,先来看一下如何反转链表前N个结点:

// 将链表的前 n 个节点反转(n <= 链表长度)
ListNode* reverse_n(ListNode* head, int n)

比如说对于下图链表,执行 reverse_n(head, 3):
LeetCode 206. 反转链表 & 92. 反转链表 II(玩出花的递归)_第8张图片
解决思路和反转整个链表差不多,只要稍加修改即可:

	ListNode* successor = nullptr; // 后驱结点
	
	// 反转以 head 为起点的 n 个结点,返回新的头结点
    ListNode* reverse_n(ListNode* head, int n){ 
    	// base case
        if(n==1){
        	// 记录第 n + 1 个结点
            successor = head->next;
            return head;
        }
        // 以 head->next 为起点,需要反转前 n - 1 个结点
        ListNode* last = reverse_n(head->next,n-1);
        head->next->next = head;
        // 让反转之后的 head 结点和后面的结点连起来
        head->next = successor;
        return last;
    }

具体的区别:

1、base case 变为 n == 1,反转一个元素,就是它本身,同时要记录后驱节点。

2、刚才我们直接把 head->next 设置为 null,因为整个链表反转后原来的 head 变成了整个链表的最后一个节点。但现在 head 节点在递归反转之后不一定是最后一个节点了,所以要记录后驱 successor(第 n + 1 个节点),反转之后将 head 连接上。
LeetCode 206. 反转链表 & 92. 反转链表 II(玩出花的递归)_第9张图片

2.1.2 反转链表的一部分

现在解决最开始提出的问题,给一个索引区间 [m,n](索引从 1 开始),仅仅反转区间中的链表元素。

ListNode* reverseBetween(ListNode* head, int begin, int end)

首先,如果 m == 1,就相当于反转链表开头的 n 个元素嘛,也就是我们刚才实现的功能:

    ListNode* reverseBetween(ListNode* head, int m, int n){
   		// base case
        if(m==1)
        	// 相当于反转前 n 个元素
            return reverse_n(head,n);
        // ...
    }

如果 m != 1 怎么办?如果把 head->next 的索引视为 1 的话,那么相对于 head->next,反转的区间应该是从第 m - 1 个元素开始的,相应的,区间结束位置也变成了 n - 1(要保证区间长度不变)。

因此,完整代码如下:

    ListNode* reverseBetween(ListNode* head, int m, int n){
   		// base case
        if(m==1)
        	// 相当于反转前 n 个元素
            return reverse_n(head,n);
            
        // 前进到反转的起点触发 base case
        head->next = reverse_list(head->next,m-1,n-1);
        return head;
    }

解法2. 迭代

迭代需要借助双指针和哑结点(这两个方法太有用了,感觉做链表题不会的时候就想想它们,有奇效!!!),按照题目的意思一步一步的做,细心一点也不难,只是理解了递归法之后就会觉得迭代法十分麻烦,远不如递归来的简洁清爽~

迭代法代码如下:

class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int m, int n) {
        if(!head || m==n) return head;
        int cnt = 0;
        ListNode* first_end = nullptr, *second_head = nullptr;
        ListNode* pre = new ListNode(-1), *cur = head, *res = pre; //定义哑结点res,用于返回最后的结果
        pre->next = head;
        //定位 m 的位置,存下 first_end 和 second_head 用于之后链表的拼接
        while(cur){
            if(++cnt==m){
                first_end = pre;
                second_head = cur;
                pre = cur;
                cur = cur->next;
                break;
            }
            pre = cur;
            cur = cur->next;
        }
        //局部反转,直到位置 n
        while(cur){
            ListNode* t = cur->next;
            cur->next = pre;
            pre = cur;
            cur = t;
            if(++cnt==n){
            	//完成拼接
                first_end->next = pre;
                second_head->next = cur;
                break;
            }
        }
        return res->next;
    }
};

参考文献

https://leetcode-cn.com/problems/reverse-linked-list/solution/tu-jie-liu-cheng-python3die-dai-xiang-jie-by-han-h/
https://zhuanlan.zhihu.com/p/107759633

说明:本文内容部分搬运至互联网,侵删。

你可能感兴趣的:(LeetCode 206. 反转链表 & 92. 反转链表 II(玩出花的递归))