1.题目
原题链接:LeetCode 92. 反转链表Ⅱ
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
示例 1:
输入:head = [1,2,3,4,5], left = 2, right = 4
输出:[1,4,3,2,5]
示例 2:
输入:head = [5], left = 1, right = 1
输出:[5]
提示:
链表中节点数目为 n
1 <= n <= 500
-500 <= Node.val <= 500
1 <= left <= right <= n
2.分析与题解
思路一:先切断再拼接
思路很简单:先遍历一次链表,标记相关的节点,再把需要反转的链表部分“切”出来,将该部分反转(应用反转整个链表的方法),然后再接上。
本思路最多需要遍历两次链表。
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
//dummy节点,减少分类讨论
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode prev = dummy;
//第一步:从虚拟头节点走 left - 1 步,循环结束后prev是left节点的前一个节点
for(int i=0; i<left-1; i++){
prev = prev.next;
}
//第二步:从left的前一个节点,再走 right - left + 1 步,循环结束后rightNode是right节点
ListNode rightNode = prev;
for(int i=0; i<right - left + 1; i++){
rightNode = rightNode.next;
}
//第三步:截取并切断链表
ListNode leftNode = prev.next;//标记left节点
ListNode curr = rightNode.next;//标记right节点的下一个节点
prev.next = null;//切断链表
rightNode.next = null;//切断链表
//第四步:反转链表区间并接回
prev.next = reverseList(leftNode);
leftNode.next = curr;
return dummy.next;
}
ListNode reverseList(ListNode head){
ListNode prev = null, curr = head, next;
while(curr!=null){
next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
}
时间复杂度:O(N)
空间复杂度:O(1)
思路二:头插法
前两个思路都是迭代,时间复杂度都是O(n)。但头插法只需要一次遍历,效率更高,而且也更加清晰。
下图展示了头插法的过程,来自力扣官方题解:
“头插”核心代码:
for(int i=0; i<right - left; i++){
ListNode next = curr.next;
curr.next = next.next;
next.next = prev.next;
prev.next = next;
}
全部代码:
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode prev = dummy;
for(int i=0; i<left-1; i++){
prev = prev.next;
}
ListNode curr = prev.next;
for(int i=0; i<right - left; i++){
ListNode next = curr.next;
curr.next = next.next;
next.next = prev.next;
prev.next = next;
}
return dummy.next;
}
}
思路三:递归
首先考虑一个问题:如何反转链表的前n个节点呢?
示例3:
输入:head = [1,2,3,4,5], n = 3
输出:[3,2,1,4,5]
示例4:
输入:head = [5], n = 1
输出:[5]
用迭代自然不难,但现在是递归思路,要体现递归的思想。怎么做呢?其实和递归反转全部链表是一样的:1.base case,2.调用自身,3.修改next指向关系。
base case:提示里已经说了,链表节点数不为零。所以base case就是n == 1的时候,这时不需要反转,直接返回就行。
调用自身:ListNode newHead = reverseN(head.next, n-1);
,直接转化为把第二个节点作为首节点的反转问题。
修改next指向关系:还是第二个节点和首节点的next指针。第二个节点指向首节点。首节点指向n+1个节点(或null),在base case那里标记一下就行了。
ListNode mark = null;
public ListNode reverseN(ListNode head, int n){
if(n==1){
mark = head.next;
return head;//n==1是base case,不用反转,直接返回
}
ListNode newHead = reverseN(head.next, n-1);
head.next.next = head;
head.next = mark;
return newHead;
}
下面是示意图:
现在“反转链表的前n个节点”解决了,而这个问题,就是原问题的base case,也就是left == 1的情况。
然后递归调用自身:reverseBetween(head.next, left-1, right-1)
,再把指针改一下就行了,代码如下:
//Java
//递归
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
if(left==1) return reverseN(head, right);
head.next = reverseBetween(head.next, left-1, right-1);
return head;
}
ListNode mark = null;
public ListNode reverseN(ListNode head, int n){
if(n==1){
mark = head.next;
return head;//n==1是base case,不用反转,直接返回
}
ListNode newHead = reverseN(head.next, n-1);
head.next.next = head;
head.next = mark;
return newHead;
}
}
下面是示意图: