上回说到链表反转是学历链表的重中之重,而链表反转的拓展问题更是高频问题,本文来集中研究一下指定区间反转问题
指定区间反转
LeetCode92:给你单链表的头指针 head
和两个整数 left
和 right
,其中 left <= right
。请你反转从位置 left
到位置 right
的链表节点,返回 反转后的链表 。
解法1. 头插法
反转核心思路如下:
每次都先让p指向q的后继,然后让q指向p(也就是pre的后继),最后让pre指向q
来看下代码:
ListNode* reverseBetween(ListNode* head, int left, int right) {
ListNode* res = (ListNode*)malloc(sizeof(ListNode));
res->next = head;
ListNode* pre = res;
// 初始让pre指向反转区间的前一个节点
for(int i = 0; i < left - 1; i++) {
pre = pre->next;
}
// 定义工作指针
ListNode* p = pre->next;
ListNode* q = p->next;
// 只对区间节点操作
for(int i = 0; i < right - left; i++) {
// 核心操作
p->next = q->next;
q->next = pre->next;
pre->next = q;
// 更新工作指针
q = p->next;
}
return res->next;
}
解法2. 穿针引线法
先举一个例子,比如我们需要把蓝色区间翻转
我们可以把这个链表分成三部分,把中间部分翻转,然后在拼接回去,如下图:
- 先把待翻转区域翻转
- 把 pre 的 next 指针指向反转以后的链表头节点,把反转以后的链表的尾节点的 next 指针指向 succ。
细节方面我们直接看代码:
ListNode* reverseBetween(ListNode* head, int left, int right) {
ListNode* ans = (ListNode*)malloc(sizeof(ListNode));
ans->next = head;
ListNode* pre = ans;
// 初始化四个指针
// 1. 让pre指向待翻转区间的左边第一个元素
for(int i = 0; i < left - 1; i++)
pre = pre->next;
// 2. 让rightNode指向待翻转区间的右边界元素
ListNode* rightNode = pre;
for(int i = 0; i < right - left + 1; i++)
rightNode = rightNode->next;
// 3. 让left指向待翻转区间的左边界元素
ListNode* leftNode = pre->next;
// 4. 让succ指向待翻转区间的右边第一个元素
ListNode* succ = rightNode->next;
// 断开链接分成三部分
pre->next = NULL;
rightNode->next = NULL;
// 区间反转
reverse(leftNode);
// 拼接三部分
leftNode->next = succ;
pre->next = rightNode;
return ans->next;
}
ListNode* reverse(ListNode* head) {
ListNode* pre = NULL;
ListNode* p = head;
while(p != NULL) {
ListNode* q = p->next;
p->next = pre;
pre = p;
p = q;
}
return pre;
}
这里需要注意的点是要先断开链接再反转,顺序不能反了。reverse方法用到的就是上一篇编程导航算法通关村第二关 | 终于学会链表翻转啦写到的直接操作链表实现翻转,这个算法很重要,一定要掌握