链表指定区间反转,基础还是链表的反转。但是多了一些要考虑的情况,这里简单介绍两种方法:
1.带虚拟结点的反转
定位到链表反转区间的前一个结点,接下来遍历要反转的区间,每遍历一个结点,就把它插到反转区间的头部,如图所示:
上代码:
/**
* 指定区间反转 带虚拟节点
*
* @param head
*/
public static ListNode specifiedSectionReverse1(ListNode head, Integer left, Integer right) {
//建立虚拟节点指向头部,从虚拟节点开始,这样可以避免讨论头节点也在反转区间内的情况
ListNode virNode = new ListNode(-1);
virNode.next = head;
ListNode pre = virNode;
for (int i = 0; i < left - 1; i++) {
pre = pre.next;
}
ListNode cur = pre.next;
while (right > left) {
ListNode next = cur.next;
cur.next = next.next;
next.next = pre.next;
//把结点丢到反转区间头部
pre.next = next;
right--;
}
return virNode.next;
}
2.直接反转
根据反转区间,把链表分成三段:链表前面部分,要反转的部分,链表后面部分,然后调用方法把中间那段要反转的链表反转一遍,最后三段链表拼在一起:
定位到四个位置,然后切割
中间的链表反转后,最后直接拼上去
代码如下:
/**
* 指定区间反转 不带虚拟节点
*
* @param head
*/
public static ListNode specifiedSectionReverse2(ListNode head, Integer left, Integer right) {
ListNode preNode, leftNode, rightNode, succNode;
//动用双指针,走完一遍后,preNode是第一条链表尾节点,leftNode是中间链表开始结点,
// rightNode是中间链表结束结点,succNode是第三条链表开始结点
//创建虚拟节点的next指向头节点,避开头节点也在反转范围的一大堆情况讨论
ListNode virNode = new ListNode(-1);
virNode.next = head;
ListNode fast = virNode;
while (right - left + 1 > 0) {
fast = fast.next;
right --;
}
ListNode slow = virNode;
for (int i = 0; i < left - 1; i++) {
slow = slow.next;
fast = fast.next;
}
preNode = slow;
leftNode = slow.next;
rightNode = fast;
succNode = fast.next;
//记得把中间链表最后节点指向null,否不然第三段链表与第二段链表连在一起,会在下面反转时一起反转
rightNode.next = null;
reverListNode(leftNode);
preNode.next = rightNode;
leftNode.next = succNode;
return virNode.next;
}
//反转链表
public static ListNode reverListNode(ListNode head){
ListNode cur = head;
ListNode pre = null;
while (cur != null){
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
值得注意的是,java对象传参传的是地址,中间的链表已经被反转后,specifiedSectionReverse2函数里的 rightNode 和 leftNode 依旧是原来的对象地址,直接用 preNode 的 next 指向 leftNode,用 rightNode 的next 指向 succNode 就成功拼接在一起了
目录