指定区间反转问题
1、问题描述
本题来自LeetCode92:给你单链表的头指针head和两个整数left和right,其中left <= right。请你反转从位置left到位置right的链表节点,返回反转后的链表。
示例:
输入:head = [1, 2, 3, 4, 5], left = 2, right = 4
输出:[1, 4, 3, 2, 5]
图示:
这题跟上篇描述的反转链表问题类似,只不过是把整个链表变为了链表中的一部分,有上一篇的解放为基础,本题依旧提供两种方式去解决。
2、解法一:头插法
如果left和right之间的区域很大,恰好是链表的头节点和尾节点时,找到left和right需要遍历一次,反转它们之间的链表还需要遍历一次,虽然总的时间复杂度为O(n),但是遍历了链表两次,这里提供了只遍历一次的方法,链表的序列如下:
有颜色填充的部分是需要反转的。
反转的整体思想是,在需要反转的区间里,每遍历到一个节点,让这个新节点来到反转部分的起始位置。下面的图展示了整个流程。
调整节点的过程就是带虚拟节点的插入操作,每走一步就要考虑各种指针怎么指,既要将节点摘下来接到对应的位置上,还要保证后续节点能够找到。
代码如下:
public ListNode reverseBetween(ListNode head, int left, int right) {
//设置 dummyNode 是这一类问题的一般做法
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;
}
3、解法二:穿针引线法
这种方法其实就是复用之前的链表反转方法,但是实现难度会更高一点。还是以上面的链表为例。
我们可以先确定好需要反转的部分,也就是题目给出的left和right之间,将其切出来,这样原链表就成了三段链表,我们将需要反转的部分用链表反转的方法反转之后,再将三段链表拼接起来。这种方法类似裁缝一样,找准位置剪下来,再缝回去,所以叫穿针引线法。
算法步骤如下:
1、先找到三段链表;
2、将待反转的区域反转;
3、把pre的next指针指向反转后的链表头节点,把反转后的链表的尾节点的next指针指向succ。
代码如下:
public ListNode reverseBetween(ListNode head, int left, int right) {
// 因为头节点有可能发生变化,所以使用虚拟节点来避免复杂的分类讨论
ListNode dummyNode = new ListNode(-1);
dummyNode.next = head;
ListNode pre = dummyNode;
//第一步:从虚拟节点走left-1步,来到left节点的前一个节点
for (int i = 0; i < left -1; i++){
pre = pre.next;
}
//第二步:从pre再走right-left+1步,来到right节点
ListNode rightNode = pre;
for (int i = 0; i < right - left + 1; i++){
rightNode = rightNode.next;
}
//第三步:切出一个子链表
ListNode leftNode = pre,next;
ListNode succ = rightNode.next;
//如果这里不设置成null,在调用链表反转方法的时候会把rightNode之后的节点也加入反转的链表中
rightNode.next = null;
//第四步:反转链表的子区间
reverseList(leftNode);
//第五步:接回原来的链表中
//因为反转之后,原来子链表的尾节点变为头节点,所以这里直接用rightNode
pre.next = rightNode;
left.next = succ;
return dummyNode.next;
}
private void reverseList(ListNode head) {
ListNode pre = null;
ListNode cur = head;
while(cur != null) {
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
}
4、总结
指定区间反转其实就是链表反转基础上更进了一步,从整个链表变为了部分链表,其实只用将这部分链表提取出来,就又退还成了链表反转问题,在处理方法上是类似的,只用把一些细节处处理好就可以了。