翻转链表大概可以分为如下四个,难度可以说是逐步增大的
- 翻转整个链表(递归和非递归)
- 翻转链表中的一部分
- 按k个一组进行翻转(从前往后)
- 按k个一组进行翻转(从后往前)
首先给出节点类定义
class ListNode{
int val;
ListNode next;
public ListNode(int val){
this.val = val;
}
}
1. 翻转整个链表
- 递归翻转整个链表,需要注意的是要记得head.next置为空,避免形成环路。
public ListNode reverseListRecursive(ListNode head) {
if(head == null || head.next == null) return head;
ListNode reversed = reverseList(head.next);
head.next.next = head;
head.next = null;
return reversed;
}
- 迭代翻转链表,因为翻转后原本的头结点会变成尾结点,因此开始循环前需要将头结点的next置为空。
public ListNode reverseList(ListNode head) {
if(head == null) return head;
ListNode prev = head, cur = head.next;
prev.next = null;
while(cur != null){
ListNode temp = cur.next;
cur.next = prev;
prev = cur;
cur = temp;
}
return prev;
}
结果如下:
2. 翻转部分链表
翻转序号fromIndex到toIndex之间的所有结点,假设链表头结点序号为1,需要注意的是fromIndex和toIndex可能会超出链表长度,此时的话不进行任何操作,直接返回。对于链表问题,声明dummy头部结点有时会带来好处,这里的话可以避免当fromIndex为1时需要进行额外的判断。
首先找到需要翻转的部分,然后进行翻转(迭代),需要注意的是翻转的时候,需要保存翻转链表的前缀结点fromPre和后缀结点toNext,以便于后面重新拼接。
public ListNode reverseList(ListNode head, int fromIndex, int toIndex){
if(fromIndex <= 0 || toIndex <= 0) return head;
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode fromPre = null, from = null, to = null, toNext = null;
ListNode cur = dummy;
int count = 0;
while(cur != null){
if(count == fromIndex-1) fromPre = cur;
if(count == fromIndex) from = cur;
if(count == toIndex){
to = cur;
toNext = cur.next;
break;
}
cur = cur.next;
count++;
}
if(from == null || to == null) return head; //fromIndex或者toIndex超出链表长度时
fromPre.next = null;
to.next = null;
//翻转from到to
ListNode pre = from;
cur = pre.next;
pre.next = null;
while(cur != null){
ListNode temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
fromPre.next = to;
from.next = toNext;
return dummy.next;
}
翻转1号到4号的所有节点,最终结果如下所示
3. 从前往后按k个一组进行翻转
相比于前一个问题,这次是要循环的翻转,同样的在每次翻转前需要保存翻转部分的前缀结点和后缀结点。
public ListNode reverseListk(ListNode head, int k){ //每k个一组进行翻转(从前往后数)
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode cur = dummy, preStartk = null, startk = null, endk = null;
int index = 0;
boolean reverse = false; //是否需要翻转
while(cur != null){
if(index == 0) preStartk = cur;
if(index == 1) startk = cur;
if(index == k){
endk = cur;
reverse = true;
}
cur = cur.next;
index++;
if(reverse){
ListNode pre = startk, preNext = pre.next;
while(preNext != cur){
ListNode temp = preNext.next;
preNext.next = pre;
pre = preNext;
preNext = temp;
}
preStartk.next = endk;
startk.next = cur;
//进行下一组翻转
cur = startk;
index = 0;
reverse = false;
}
}
return dummy.next;
}
按3个一组(从前往后)进行翻转,结果如下
4. 从后往前按k个一组进行翻转
说实话,第三个问题从前往后翻转k个已经是Leetcode中hard级别的题目了,这个可能更难一点。主要是要想到思路,可以分为三个步骤,这两个函数在上面的问题已经实现了。
- 翻转整个链表
- 从前往后按k个每组进行翻转
- 再一次翻转整个链表
public ListNode reversedListk(ListNode head, int k){ //逆序k个一组进行翻转(从后往前数)
head = reverseList(head); //首先翻转整个链表
head = reverseListk(head, k); //然后从前往后按k个每组进行翻转
head = reverseList(head); //最后再翻转整个链表
return head;
}
按3个一组(从后往前)进行翻转,结果如下
其它的辅助代码如下所示
public static void main(String[] args) {
ListNode head = new ListNode(1), cur = head;
for(int i = 2;i<8;i++){
ListNode temp = new ListNode(i);
cur.next = temp;
cur = temp;
}
ReverseList solution = new ReverseList();
System.out.println("翻转前:"+solution.printListNode(head));
// head = solution.reverseList(head); //非递归翻转
// head = solution.reverseListRecursive(head); //递归翻转
// head = solution.reverseList(head, 1, 4); //只翻转from到to的结点
// head = solution.reverseListk(head, 3); //从前往后k个一组进行翻转
head = solution.reversedListk(head, 3); //从后往前k个一组进行翻转
System.out.println("翻转后:"+solution.printListNode(head));
}
public String printListNode(ListNode head){
StringBuffer s = new StringBuffer();
ListNode cur = head;
while(cur != null){
s.append(cur.val).append(' ');
cur = cur.next;
}
return s.toString();
}