翻轉鏈表大概可以分爲如下四個,難度可以說是逐步增大的
首先給出節點類定義
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();
}