大链表吱呀吱悠悠地转♪
这里~的风景呀真奇怪♪
天在转♪ 地在转♪
还有一个刷题的小笨蛋♪
反转链表!经久不衰的送分 题!
反转整个链表
206.反转链表
给你单链表的头节点head
,请你反转链表,并返回反转后的链表。
提示:
链表中节点的数目范围是[0, 5000]
-5000 <= Node.val <= 5000
迭代和递龟 递归都可以哦!
迭代解法
遍历整个链表,反转一下next指针的方向就好了,非常简单,不赘述啦!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution {public : ListNode* reverseList (ListNode* head) { ListNode* prev = nullptr ; ListNode* curr = head; while (curr) { ListNode* next = curr->next; curr->next = prev; prev = curr; curr = next; } return prev; } };
递归解法
递归的版本就比较复杂一些,关键在于反向工作。假设链表的其余部分已经被反转,那么考虑如何反转之前的部分。
假设链表长这样:
$$
n_1 \rightarrow \dots n_{k - 1} \rightarrow n_k \rightarrow n_{k + 1} \rightarrow \dots \rightarrow n_m \rightarrow nullptr
$$
如果我们已经将$n_{k + 1} \rightarrow \dots \rightarrow n_m$的部分反转,目前来到了$n_k$。那么我们希望$n_k$的下一个结点$n_{k + 1}$能指向$n_k$。即:
$$
n_k\rightarrow next\rightarrow next = n_k
$$
那就开始递归吧!从后往前反转链表,就一直一直往后冲。但是需要一个出口呀,这里设置当结点为空或到达链表末端时返回该结点,相当于是新链表的头部啦!之后就是之前分析的部分啦,执行head->next->next = head;
更改指针的方向。但是要记得把当前指针的next
设为空哦,不然就会有环啦!
1 2 3 4 5 6 7 8 9 10 11 12 class Solution {public : ListNode* reverseList (ListNode* head) { if (!head || !head->next) { return head; } ListNode* newHead = reverseList (head->next); head->next->next = head; head->next = nullptr ; return newHead; } };
反转部分链表
反转所有链表还是太简单了(单指迭代,上点有意思的:反转部分链表!
92.反转部分链表 II
截取需要反转部分单独操作
最容易想到的思路就是,借鉴之前反转整个链表的想法,弄四个指针,两个指针在开始反转部分的前一个和开始部分,设为pre
和leftNode
;两个指针在反转部分的结束部分和后一个结点,设为rightNode
和curr
。将pre
和rightNode
的next
设为空,将需要反转的部分独立出来,单独进行反转。反转完成后再接回原来的链表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 class Solution {private : void reverseLinkedList (ListNode *head) { ListNode *pre = nullptr ; ListNode *cur = head; while (cur != nullptr ) { ListNode *next = cur->next; cur->next = pre; pre = cur; cur = next; } } public : ListNode *reverseBetween (ListNode *head, int left, int right) { ListNode *dummyNode = new ListNode (-1 ); dummyNode->next = head; ListNode *pre = dummyNode; for (int i = 0 ; i < left - 1 ; i++) { pre = pre->next; } ListNode *rightNode = pre; for (int i = 0 ; i < right - left + 1 ; i++) { rightNode = rightNode->next; } ListNode *leftNode = pre->next; ListNode *curr = rightNode->next; pre->next = nullptr ; rightNode->next = nullptr ; reverseLinkedList (leftNode); pre->next = rightNode; leftNode->next = curr; return dummyNode->next; } };
一次遍历
上一个方法当需要反转的区间长度非常大的时候,时间开销会变大,极端情况下需要遍历两次链表。考虑使用“头插法”实现一次遍历的反转部分链表。
整体思想是:在需要反转的区间里,每遍历到一个节点,让这个新节点来到反转部分的起始位置。
下面我们具体解释如何实现。使用三个指针变量 pre
、curr
、next
来记录反转的过程中需要的变量,它们的意义如下:
curr
:指向最初的待反转区域的第一个节点;
next
:永远指向curr
的下一个节点,循环过程中,curr
变化以后next
会变化;
pre
:永远指向最初的待反转区域的第一个节点left
的前一个节点,在循环过程中不变。
(要画的图太多了,直接手写了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution {public : ListNode *reverseBetween (ListNode *head, int left, int right) { ListNode *dummyNode = new ListNode (-1 ); dummyNode->next = head; ListNode *pre = dummyNode; for (int i = 0 ; i < left - 1 ; i++) { pre = pre->next; } ListNode *cur = pre->next; ListNode *next; for (int i = 0 ; i < right - left; i++) { next = cur->next; cur->next = next->next; next->next = pre->next; pre->next = next; } return dummyNode->next; } };