一、题型总结
- 反转全部链表元素:leetcode206
- 指定区间反转:leetcode92
- 每K个元素反转:leetcode25
- 每两个元素反转:leetcode24
二、实现思路
为解决链表反转类型的题,应从简单到复杂,故首先考虑反转全部链表元素。
注:在本文中代码中,若参数为 Node *head ,则代表本函数处理的是带有dummyHead的链表;若参数为 Node *node,则代表本函数处理的是不带dummyHead的链表。
例如,在2.1中需要反转链表的全部元素,所以dummyHead的存在会影响整体代码的书写。而在2.2反转指定区间时,增加dummyHead可减少特殊情况的判断。
2.1 反转链表全部元素
反转链表全部元素,有两种常见的解题思路,即递归与迭代。通常来说,递归是更容易理解与实现的方式。
递归形式的代码块如下图所示。对以node->next头结点的子链表进行反转,显然,反转后的链表尾结点last为node->next。所以反转完成后,使用last->next = node 完成拼接。
Node *reverse1(Node *node) {
// node == nullptr是为了应对第一次就传入了nullptr的情况
// 其余情况下,当node->next == nullptr时就返回了,这是反转后链表的第一个结点
if(node == nullptr || node->next == nullptr) {
return node;
}
Node *last = node->next;
Node *first = reverse1(node->next);
last->next = node;
node->next = nullptr;
return first;
}
接下来,考虑迭代形式实现链表反转。定义两个指针has与remain,分别表示已经完成反转的子链表和还没有完成反转的子链表。初始条件,has指向头结点(next为nullptr),remain指向头结点的下一个结点。
核心思路:将remain视为一个队列,令即将出队的元素是has的先驱结点,并及时更新has。大白话就是,将现在的has部分拼接到remain指向结点的后面。
Node *reverse2(Node *node) {
if(node == nullptr) {
return node;
}
Node *has = node; // 已经完成反转的部分
Node *remain = node->next; // 还没有完成反转部分
has->next = nullptr;
while(remain != nullptr) {
Node *front = remain;
remain = remain->next;
front->next = has;
has = front;
}
return has;
}
2.2 指定区间反转
依照leetcode92的题面要求,left和right为从1开始的下标,且链表中不包含dummyHead,
但增加dummyHead后,可大大简化left为1时的情况,所以在写leetcode92时,可以自行增加一个dummyHead。
解释:
一般情况下,可将整个链表分为三个部分:指定区间之前的,指定区间,指定区间之后的。在反转指定之前需要找到第一部分的最后一个结点并记录。但,当left=1时,第一部分直接不存在了,这与其他情况不同,需要额外的代码进行处理与判断。
如果增加了dummyHead,当left=1时,在指定区间之前仍存在一个dummyHead,从而统一了操作,简化了代码的书写。
首先,遍历找到指定区间前的最后一个结点last1。然后,使用迭代的方式,反转指定区间,得到该区间的起点has和终点last2。最后,将各个部分连接起来。
// 不考虑left,right越界的问题
// left是node结点的下标,下标从1开始,dummyHead不在计数范围内
Node *ReverseBetween(Node *head, int left, int right) {
// 第一部分,最后一个结点last1
Node *last1 = head;
for(int i = 0; i < left-1; i++) {
last1 = last1->next;
}
// 第二部分,要反转的部分,起始结点为has
// 反转后,第二部分的最后一个结点为last2
Node *has = last1->next;
Node *last2 = last1->next;
Node *remain = has->next;
has->next = nullptr;
for(int i = 0; i < right-left; i++) {
Node *front = remain;
remain = front->next;
front->next = has;
has = front;
}
// 第三部分,第一个结点remain
last2->next = remain;
last1->next = has;
return head;
}
2.3 每K个元素反转
递归算法思路:
- 移动k-1步,得到这一组元素的最后一个节点cur,断开cur与之后结点的连接。
- 对cur之后的结点递归得调用反转k个元素函数。
- 调用反转链表函数调用前k个元素。
- 将反转后得结果进行拼接。
注:做好不足k个结点时的判断即可。
Node *reversek1(Node *node, int k) {
Node *cur = node;
// 移动k-1次后,此时cur是这一组最后一个结点
for(int i = 0; cur != nullptr && i < k-1; i++) {
cur = cur->next;
}
// 说明不足k个结点,直接返回
if(cur == nullptr) {
return node;
}
// cur->next是下一组的开头
Node *nextStart = cur->next;
cur->next = nullptr;
Node *newHead = reverse2(node);
node->next = reversek1(nextStart, k);
return newHead;
}
使用栈实现思路:
- 定义head和res指针。head值不变,一直指向dummyHead。res值指向完成反转的最后一个链表结点,初始值为dummyHead。
- 使用remain和cur两个指针,循环开始时两个指针值相同。不断移动cur,判断是否有k个元素。
- 如果不满足,直接break出循环,在循环外将remain及其之后的结点拼接到res之后。
- 如果满足,不断将栈中的元素依次pop出来,串联在res之后,然后更新remain结点。
注:res->next = remain其实有两个作用
① 当len(list) % k == 0时,将链表最后一个结点的next值修改为nullptr。
res->next = top; res = res->next;这两行代码表示:当前结点是A,A的下一个结点是B,更新当前结点为B,但并没有告知B的下一个结点是谁。所以在循环结束后,链表最后一个结点并不知道自己的next值是谁,需要程序员手动为其赋值为nullptr。
② 当len(list) % k != 0时,将remain及其之后的结点拼接在res之后。
Node *reversek2(Node *node, int k) {
Node *remain = node;
stack<Node*> s;
Node *head = new Node(-1);
Node *res = head;
while(1) {
Node *cur = remain;
int cnt = k;
while(cur != nullptr && cnt--) {
s.push(cur);
cur = cur->next;
}
if(cnt > 0) {
break;
}
while(s.size() != 0) {
Node *top = s.top();
s.pop();
res->next = top;
res = res->next;
}
remain = cur;
}
res->next = remain;
return head->next;
}
2.4 每两个元素反转
和每K个元素反转一样,使用递归,或者直接调用每K个元素的方法。
Node *reversePair(Node *node) {
if(node == nullptr || node->next == nullptr) {
return node;
}
Node *n1 = node, *n2 = node->next;
Node *nextStart = n2->next;
n1->next = reversePair(nextStart);
n2->next = n1;
return n2;
}
三、全部代码
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
struct Node {
int val;
Node *next;
Node(int v) {
val = v;
next = nullptr;
}
};
Node *NewList(vector<int> &arr) {
Node *dummyHead = new Node(-1);
Node *cur = dummyHead;
for(auto val : arr) {
cur->next = new Node(val);
cur = cur->next;
}
return dummyHead;
}
void DeleteList(Node *head) {
while(head != nullptr) {
Node *tmp = head;
head = head->next;
delete tmp;
tmp = nullptr;
}
}
// 递归形式反转链表,并返回反转后的头结点
Node *reverse1(Node *node) {
if(node == nullptr || node->next == nullptr) {
return node;
}
Node *last = node->next;
Node *first = reverse1(node->next);
last->next = node;
node->next = nullptr;
return first;
}
// 迭代方式实现反转链表
Node *reverse2(Node *node) {
if(node == nullptr) {
return node;
}
Node *has = node; // 已经完成反转的部分
Node *remain = node->next; // 还没有完成反转部分
has->next = nullptr;
while(remain != nullptr) {
Node *front = remain;
remain = remain->next;
front->next = has;
has = front;
}
return has;
}
// 使用递归方式实现每k个元素反转一次
Node *reversek1(Node *node, int k) {
Node *cur = node;
// 移动k-1次后,此时cur是这一组最后一个结点
for(int i = 0; cur != nullptr && i < k-1; i++) {
cur = cur->next;
}
// 说明不足k个结点,直接返回
if(cur == nullptr) {
return node;
}
// cur->next是下一组的开头
Node *nextStart = cur->next;
cur->next = nullptr;
Node *newHead = reverse2(node);
node->next = reversek1(nextStart, k);
return newHead;
}
// 使用栈实现每k个元素反转一次
Node *reversek2(Node *node, int k) {
Node *remain = node;
stack<Node*> s;
Node *head = new Node(-1);
Node *res = head;
while(1) {
Node *cur = remain;
int cnt = k;
while(cur != nullptr && cnt--) {
s.push(cur);
cur = cur->next;
}
if(cnt > 0) {
break;
}
while(s.size() != 0) {
Node *top = s.top();
s.pop();
res->next = top;
res = res->next;
}
remain = cur;
}
res->next = remain;
return head->next;
}
Node *reversePair(Node *node) {
if(node == nullptr || node->next == nullptr) {
return node;
}
Node *n1 = node, *n2 = node->next;
Node *nextStart = n2->next;
n1->next = reversePair(nextStart);
n2->next = n1;
return n2;
}
Node *ReverseList(Node *head) {
Node *cur = head->next;
if(cur == nullptr) {
return head;
}
// Node *first = reverse1(cur);
Node *first = reverse2(cur);
head->next = first;
return head;
}
// 不考虑left,right越界的问题
// left是node结点的下标,下标从1开始,dummyHead不在计数范围内
Node *ReverseBetween(Node *head, int left, int right) {
// 第一部分,最后一个结点last1
Node *last1 = head;
for(int i = 0; i < left-1; i++) {
last1 = last1->next;
}
// 第二部分,要反转的部分,起始结点为has
// 反转后,第二部分的最后一个结点为last2
Node *has = last1->next;
Node *last2 = last1->next;
Node *remain = has->next;
has->next = nullptr;
for(int i = 0; i < right-left; i++) {
Node *front = remain;
remain = front->next;
front->next = has;
has = front;
}
// 第三部分,第一个结点remain
last2->next = remain;
last1->next = has;
return head;
}
Node *ReverseEveryK(Node *head, int k) {
if(k == 1) {
return ReverseList(head);
}
// head->next = reversek1(head->next, k);
head->next = reversek2(head->next, k);
return head;
}
Node *ReversePair(Node *head) {
// return ReverseEveryK(head, 2);
head->next = reversePair(head->next);
return head;
}
void PrintList(Node *head) {
Node *cur = head->next;
while(cur != nullptr) {
cout << cur->val << " ";
cur = cur->next;
}
cout << endl;
}
int main() {
vector<int> arr{1, 2, 3, 4, 5, 6, 7, 8};
Node *list = NewList(arr);
PrintList(list);
list = ReversePair(list);
PrintList(list);
DeleteList(list);
}