92. 反转链表 II
很显然,对于反转链表的题来说,会有递归和迭代两种解法。
如果用递归,建议先熟悉下面的普通反转链表问题,一步步推导到指定范围内的链表反转问题:
迭代法:
这题,我的做法可行,但是很绕,很容易出现紧张就理不清楚思路的问题:
实现了:一次遍历,完成翻转部分链表的结果。
整体思路就是:从头开始找到要反转的起始结点,并保存交界处的两个结点;不断反转到需要反转的终点,保存好交界处的两个结点,最后交叉相连即可
——注意:一定要在纸上画出流程,不然肯定做不出来的
链表: 前头 – 前尾 --> 中头 – 中尾 -> 后头 – 后尾
反转之后: 前头 – 前尾 --> 中尾 – 中头 -> 后头 – 后尾
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
if(left == right) return head;
int count = 0;
ListNode dummy = new ListNode(0, head);
// unchange表示未修改的部分的尾巴,tail是指翻转之后的尾巴结点(未反转前的反转起点),
// cur、prev和next是在翻转过程中用到的
ListNode unchange = dummy, tail = dummy, cur, prev, next;
while(count < left){
unchange = tail;
tail = tail.next;
count++;
}
// 这边找到了要反转的起点:tail就是反转的起点(反转结束之后就是尾巴)
// unchange就是前面未反转的尾巴(用来最后连接用的)
cur = tail;
prev = null;
while(count <= right){
next = cur.next;
cur.next = prev;
prev = cur;
cur = next;
count++;
}
// 要反转的部分全部做了反转,此时prev指向的是反转的尾巴(反转之后的首部),cur/next指向的是最后未反转的头部(用来连接用)
unchange.next = prev; // 前尾--中尾
tail.next = cur; // 中头 -- 后头
return dummy.next;
}
}
——为了防止从头开始反转,所以设置了dummy结点,用来保证反转之后也能找到头结点
递归方法:再下面的基础上可以自然的推出来:
可以将其看成:
——先获取前面不需要反转的部分,然后将后面要反转的部分当成是从头开始反转部分链表问题
class Solution {
// 只是用来做反转的,从头开始反转部分的解法
private ListNode recurive(ListNode head, int N){
if(N == 1) return head;
ListNode res = recurive(head.next, N - 1);
ListNode temp = head.next.next;
head.next.next = head;
head.next = temp;
return res;
}
public ListNode reverseBetween(ListNode head, int left, int right) {
if(left == right) return head;
ListNode cur = head, prev = head;
int count = 1;
if(left == 1) return recurive(head, right); // 如果left=1,就是从头开始反转
while(count < left){
prev = cur;
cur = cur.next;
count++;
}
// 到这边,就找到了后面需要反转的起始结点,prev指向的是未反转的尾结点;cur=next指向的是反转的头结点
prev.next = recurive(cur, right - left + 1);
return head;
}
}
还可以再优化:
class Solution {
// 只是用来做反转的
private ListNode recurive(ListNode head, int N){
if(N == 1) return head;
ListNode res = recurive(head.next, N - 1);
ListNode temp = head.next.next;
head.next.next = head;
head.next = temp;
return res;
}
public ListNode reverseBetween(ListNode head, int left, int right) {
if(left == 1) return recurive(head, right);
else head.next = reverseBetween(head.next, left - 1, right - 1); // 核心,不断地推进到要反转地起点
return head;
}
}
基础:206. 反转链表 & 剑指 Offer 24. 反转链表
最常规题,一般这种就要求直接写出来,连面试题中都不会出现的(出现,应该也是考察多解法的)
迭代法:
——需要有3个指针,prev、cur、next,prev初始为null,它们分别保存当前结点的前一个、当前结点、当前结点的后一个
- 当前结点的prev,就是反转之后,当前结点的next
- 当前结点
- 当前结点的next,反转之后,就是当前结点的prev,因为当前结点的next已经断掉,所以为了能成功找到next结点,需要提前保存
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null) return head;
ListNode prev = null, cur = head, next;
while(cur != null){
next = cur.next;
cur.next = prev;
prev = cur;
cur = next;
}
return prev;
}
}
——具体看代码,且要在纸上画出来。(反正,毛想想我是想不出来的)
稍微有难度的是,递归解法,也需要牢记的。
递归需要记住的是:当前结点在反转的时候,其后面的结点都已经反转完了,递归是递归到结点尾部,然后再反转:
由于递归到结点尾了:1->2->3->4->5->null:
- 此时head在5,5就是未来的头结点,且5->next = null,选择直接返回
- 此时head在4,收到了返回值res为5,此时4->next=5,所以需要将5->next=4,那么5已经反转完成,而为了格式统一4->next=null
——总结,递归就是从链表尾部开始反转到,到某个结点时,其后面的结点已经完成了反转
class Solution {
public ListNode reverseList(ListNode head) {
if(head== null || head.next == null) return head; // 处理特殊情况,也是递归的终止条件
ListNode res = reverseList(head.next);
head.next.next = head;
head.next = null;
return res; // 需要保存新的链表头,就是原来的尾部
}
}
ps:扩展
只反转前面n个结点,即 1->2->3->4->5->null,反转前面4个 => 4->3->2->1->5->null
可以发现,该题目是兼容上面的反转整个链表的。
只考虑递归的方法,如果直接想可能解不出来,需要先考虑到完全反转的递归做法,然后再此基础上想递归做法:带上计数器
思路也是递归到反转的尾部,然后
class Solution {
public ListNode reverseBetween(ListNode head, int N) {
if(N == 1) return head;
ListNode res = reverseBetween(head.next, N-1);
ListNode temp = head.next.next; // 保留当前结点的后继的后继(维护的是未反转的头结点)
head.next.next = head; // 当前结点的后继结点的next指向当前结点
head.next = temp; // 当前结点更新其next结点
return res;
}
}
pps:迭代的方法:
class Solution {
public ListNode reverseBetween(ListNode head, int N) {
if(left == 1) return head; // 边界条件
ListNode prev = null, cur = head, next;
int count = 1;
while(count <= left){
next = cur.next;
cur.next = prev;
prev = cur;
cur = next;
count++;
}
head.next = cur; // 此时的head就是反转后的尾巴,此时的cur/next就是未翻转的首部
return prev; // 此时的prev就是反转后的头
}
参考:https://leetcode-cn.com/problems/reverse-linked-list-ii/solution/bu-bu-chai-jie-ru-he-di-gui-di-fan-zhuan-lian-biao/