反转链表

链表节点结构体定义:

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  */
	}
};
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nepu_bin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值