链表节点结构体定义:
struct ListNode {
int val;
ListNode* next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode* next) : val(x), next(next) {}
};
每个节点包含 val 值以及 指向下一个节点的指针,结构体中定义实现了构造函数的三种重载。
力扣206:反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
迭代求解
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* prev = head;//前指针
ListNode* rear = nullptr;//后指针
while (prev) {
ListNode* tmp = prev->next;//记录前指针下一个节点的位置
prev->next = rear;
rear = prev;//指针前移
prev = tmp;//指针前移
}
return rear;
}
};
一次遍历,时间复杂度O(N),使用了常数空间,空间复杂度O(1);
递归解法
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (!head || !(head->next))
return head;
ListNode* ret = reverseList(head->next);//ret为返回得到的头结点
head->next->next = head;//扭转head节点下一节点的指向
head->next = nullptr;//当前节点指向空
return ret;
}
};
一次遍历,时间复杂度O(N),使用了隐式内存栈,深度为N,空间复杂度O(N);
借助头结点的双指针迭代
类似于普通的迭代解法,巧妙之处在于借助了头结点。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (!head) return nullptr;
ListNode* cur = head;
while (head->next != nullptr) {
ListNode* tmp = head->next->next;
head->next->next = cur;//调整head节点后一个节点的指向
cur = head->next;
head->next = tmp;
}
}
};
一次遍历,时间复杂度O(N),使用了常数空间,空间复杂度O(1);
92. 反转链表 II
>>>>>>>>>>>>>>>>>>LeetCode原题传送>>>>>>>>>>>>>>>>
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
输入:head = [1,2,3,4,5], left = 2, right = 4
输出:[1,4,3,2,5]提示:
链表中节点数目为n
1 <= n <= 500
-500 <= Node.val <= 500
1 <= left <= right <= n进阶: 你可以使用一趟扫描完成反转吗?
一次遍历,迭代解法
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int left, int right) {
ListNode* virNode = new ListNode(-1,head);//生成一个指向头结点的伪节点
ListNode* cur = head;
ListNode* prev = virNode;
int distance = left - 1;
while (distance--) {//移动distance次使cur到达第一个需要反转的节点处
cur = cur->next;
prev = prev->next;
}//注:这里可以只让cur走到第一个需要反转节点前,此时再声明 prev 指针,让cur再走一步即可。
//为了代码思路更为清晰,这里直接声明两个指针进行操作。
//此时 prev 已经指向 需要反转元素的前一节点
ListNode* tmp = new ListNode(cur->val);
ListNode* rec = tmp;//保存tmp位置,用于后续的链表拼接
int gap = right - left;
while (gap--) {
ListNode* newNode = new ListNode(cur->val,tmp);//用一个新的链表生成反转后的元素
tmp = newNode;//tmp往前移动,迎接下一节点
cur = cur->next;
}
//此时cur指向需要反转部分的末尾的下一位置
//tmp为反转部分链表的头结点
rec->next = cur;//将新链表的尾部与原链表连接起来
prev->next = tmp;//将新链表的头部与原链表连起来
return virNode->next;
/* 转载请注明出处 https://blog.csdn.net/Genius_bin */
}
};
一次遍历:时间复杂度O(N),使用了常数空间O(1);
我们注意到:上一种方法使用虚节点对需要反转的部分构建了一个新链表,保存断开的位置最后将其连接起来,使用了常数空间,但当需要反转的元素区间不断增大时,这时耗费的空间将接近O(N),因此我们可以用下面的方法节省这块空间
穿针引线法
class Solution {
public:
void reverseList(ListNode* head) {//第一题,反转单链表
ListNode* pre = nullptr;
ListNode* cur = head;
while (cur) {//节点不为空
ListNode* tmp = cur->next;//保存后一节点
cur->next = pre;
pre = cur;
cur = tmp;
}
}
ListNode* reverseBetween(ListNode* head, int left, int right) {
ListNode* virNode = new ListNode(-1, head);//生成一个指向头结点的伪节点
//从需要反转处切断
ListNode* cur = virNode;
int distance = left - 1;
while(distance--){
cur = cur->next;
}//cur位于需要反转处的前一节点
ListNode* firtmp1 = cur;//保存第一个切口的前节点
cur = cur->next;//向后走一步到第一个需要反转的元素上
ListNode* firtmp2 = cur;//保存第一个切口的后节点
firtmp1->next = nullptr;//从需要反转处切开
int gap = right - left;
while (gap--){
cur = cur->next;
}//cur走到最后一个需要反转的节点上
ListNode* sectmp1 = cur;//保存第二个切口的前节点
cur = cur->next;
ListNode* sectmp2 = cur;//保存第二个切口的后节点
sectmp1->next = nullptr;
reverseList(firtmp2);//反转局部元素
firtmp1->next = sectmp1;
firtmp2->next = sectmp2;
return virNode->next;
/* 转载请注明出处 https://blog.csdn.net/Genius_bin */
}
};
时间复杂度:O(N),其中 N 是链表总节点数。最坏情况下,需要遍历整个链表,空间复杂度O(1);
一次遍历 + 头插法
这里引用力扣官方题解的一段解释:
上一种方法:如果 left 和 right 的区域很大,恰好是链表的头节点和尾节点时,找到 left 和 right 需要遍历一次,> 反转它们之间的链表还需要遍历一次,虽然总的时间复杂度为 O(N),但遍历了链表 2 次,
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int left, int right) {
ListNode* virNode = new ListNode(-1, head);//生成一个指向头结点的伪节点
ListNode* prev = virNode;
int distance = left - 1;
while (distance--) {
prev = prev->next;
}//cur位于需要反转处的前一节点
ListNode* cur = prev->next;//记录此时位置
int times = right - left;//一共插入 times 次
while (times--) {
ListNode* next = cur->next;
cur->next = next->next;
next->next = prev->next;
prev->next = next;
}
return virNode->next;
/* 转载请注明出处 https://blog.csdn.net/Genius_bin */
}
};