Leetcode一起攻克鏈表


鏈表需要我們掌握的題目不是很多,只要掌握這20幾道最最經典的題目,應對面試絕對綽綽有餘。

題目鏈接

1.兩數相加:完成題解
2.合併兩個有序鏈表:完成題解
3.排序鏈表:完成題解(需要再做)
4.反轉鏈表:完成題解
5.兩兩交換鏈表中的節點:完成題解
6.相交鏈表:完成題解(需要再做)
7.K 個一組翻轉鏈表
8.反轉鏈表 II回顧的時候再做,不太會啊。
9.刪除排序鏈表中的重複元素:完成題解
10.刪除鏈表的倒數第N個節點:完成題解
11.迴文鏈表:完成題解(需要再做)
12.分隔鏈表
13.奇偶鏈表:完成題解(需要再做)
14.合併K個排序鏈表:完成題解(分治算法需要再做)
15.重排鏈表:完成題解。
16.有序鏈表轉換二叉搜索樹
17.環形鏈表 II
18.對鏈表進行插入排序:完成題解(需要再做)

題目分類

鏈表基礎

1.兩數相加

思路

非常簡單的一道鏈表題,我們需要考慮的就是當前鏈表的值相加後是否會大於等於10,我們設置一個boolean變量addone,如果大於10就賦值爲true。
整體思路先是
1.兩個鏈表同時判斷,每次都將鏈表向後移,直到一個鏈表爲空
2.分別判斷兩個鏈表是否還不爲空,不爲空就逐個後移(注意這裏不能直接讓我們的result.next=不空的鏈表,因爲可能我們的addone爲true,所以要加上1)
3.最後還要繼續判斷addone是否爲true,爲true,繼續向result添加一個爲1的ListNode。

代碼

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode result =new ListNode(-1);
        ListNode res = result;
        //設置一個進位變量
        boolean addone = false;
        while(l1!=null&&l2!=null){
            int l1num = l1.val;
            int l2num = l2.val;
            int now = l1num+l2num;
            if(addone)
                now+=1;
            if(now>=10){
                result.next = new ListNode(now-10);
                addone = true;
            }else{
                result.next = new ListNode(now);
                addone = false;
            }
            result = result.next;
            l1 = l1.next;
            l2 = l2.next;
        }
        //l1還有剩餘(不能直接result.next=l1,要一個一個往後去判斷,因爲addone可能爲true)
        while(l1!=null){
            int l1num = l1.val;
            int now = l1num;
            if(addone)
            {
                now+=1;
            }
            if(now>=10){
                result.next = new ListNode(now-10);
                addone = true;
            }else{
                result.next = new ListNode(now);
                addone = false;
            }
            l1 = l1.next;
            result = result.next;
        }
        //l2還有剩餘
         while(l2!=null){
            int l2num = l2.val;
            int now = l2num;
            if(addone)
            {
                now+=1;
            }
            if(now>=10){
                result.next = new ListNode(now-10);
                addone = true;
            }else{
                result.next = new ListNode(now);
                addone = false;
            }
            l2 = l2.next;
            result = result.next;
        }
        //防止addone還存在,還需要繼續進位
        if(addone)
        {
            result.next = new ListNode(1);
            }
        return res.next;
    }
}

2.反轉鏈表

思路

這個問題比較簡單,只要指定一個pre指針就好,每次首先提取出head的下一個指針並付給next,讓head的next指向pre,然後pre移動到當前的head,最後head賦值next即可,持續循環。直到找到我們head的next爲null時,就不將head賦值爲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) {
        ListNode pre = null;
        while(head!=null){
            ListNode next = head.next;
            head.next = pre;
            pre = head;
            if(next==null)
                break;
            head = next;
        }
        return head;
    }
}

3.相交鏈表

思路

這道題有很多思路,比如HashMap之類的思路,我們這裏只考慮最優解。

 考慮最優解:讓a節點走到a鏈表尾部的時候,去指向b鏈表的頭部
        當b節點指向b鏈表的尾部時,去指向a鏈表的頭部,這樣最終會在相交點相遇。
        雖然起點不同,但路程是相同的,最終會相遇。
        A在前面比B少走1個格,所以會先到終點,然後再走B的路,
        B雖然在前面比A多走一個格,但再走A的路時,會比A少走一格,因此最終會在c1相遇。

再來一個圖就更加清晰,假如兩個鏈表相交,我們可以設第一個鏈表不相交的部分爲a1長度,第二個鏈表不相交的部分爲b1長度,設相交長度爲c。那麼可以看出a走的是a1+c+b1,而b走的是b1+c+a1,因此是相同的路徑。
在這裏插入圖片描述

代碼

這裏要注意一下,
1.有可能有不相交的鏈表,相交的話,就是a,b節點在第二次走的過程肯定會相遇,那麼只要有一個節點第二次走到終點,那麼就說明他們肯定是不想交的。
2.如果兩個鏈表有一個爲空就不可以相交。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
       
        //一個爲空就不可能相交
        if(headA==null||headB==null)
            return null;
        ListNode a = headA;
        ListNode b = headB;
        //這裏要注意,有可能是沒有交點的,那麼當一個節點第二次到達終點就說明沒有交點
        int atime = 0;
        int btime = 0; 
        while(a!=b&&atime<2&&btime<2)
        {
            a = a.next;
            b = b.next;
            if(a==null){
                a = headB;
                atime++;
            }
            if(b==null){
                b = headA;
                btime++;
            }
        }
        if(atime==2||btime==2)
            return null;
        return a;
    }
}

4.迴文鏈表

思路

需要首先找到鏈表的中點,並將鏈表中點之後的部分鏈表全部翻轉,之後再比較兩個鏈表是否完全相等。值得一提的是,如果鏈表長度爲奇數,slow會指向中間元素,如果長度爲偶數,則會指向中間兩個值中的左邊值,因此我們第二段鏈表的頭都是slow.next。還需要注意要將兩部分斷開。

代碼

注意邊界條件,爲空的情況。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        /*
        分成兩段即可,然後翻轉後面那部分
        使用快慢指針找到中點
        */
        if(head==null)
            return true;
        ListNode fast = head;
        ListNode slow = head;
        while(fast.next!=null&&fast.next.next!=null){
            fast = fast.next.next;
            slow = slow.next;
        }
        //分開slow之後的
        ListNode pre = null;
        ListNode now = slow.next;
        //System.out.println(now.val);
        slow.next = null;
        //翻轉完成,以now爲頭部
        while(now!=null)
        {
            ListNode next = now.next;
            now.next = pre;
            pre = now;
            if(next==null)
                break;
            now = next;
        }
        //System.out.println(now.val);
        //當奇數長度時,head鏈表的長度就多包含個最中間的節點,因此要判斷當兩個都不爲空,而不是head不爲空
        while(head!=null&&now!=null)
        {
            if(head.val!=now.val){
                System.out.println("now:"+now.val+"head:"+head.val);
                return false;
            }
            head = head.next;
            now = now.next;
        }
        return true;
    }
}

5.刪除排序鏈表中的重複元素

思路

可以判斷當前head與head.next是否相等,如果相等的話就讓head.next指向head.next.next,不斷的更改head.next的值,直到head.next與head不同,再把head賦值給head.next。

代碼

/**
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if(head==null)
            return null;
            ListNode res = head;
        while(head!=null)
        {
            //一直往後走直到head.next與head不同,再把head賦給head.next
            while(head.next!=null&&head.next.val==head.val)
            {
                head.next = head.next.next;
            }
            head = head.next;

        }
        return res;
    }
}

6.刪除鏈表的倒數第N個節點

思路一:兩次遍歷

首先遍歷鏈表,得到鏈表的長度,就可以知道我們要往後走幾步以找到我們要刪除的節點。

代碼一

這裏要注意pre不是指向head,而是要指向一個新的節點,讓這個節點的下一個指向head,防止要刪除的是第一個節點。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        if(n==0||head==null)
            return head;
       
        int deepth = finddeepth(head);
        int times = deepth-n;
        //這裏用這個pre,省的要刪除第一個節點。
        ListNode pre = new ListNode(-1);
        pre.next = head;
        ListNode res = pre;
        //讓head指向需要刪除的節點,讓pre指向需要刪除節點前面的節點
        while(times>0){
            pre = pre.next;
            head = head.next;
            times--;
        }
        pre.next = head.next;
        return res.next; 
    }
    public int finddeepth(ListNode head)
    {
        int i = 0;
        while(head!=null)
        {
            i++;
            head = head.next;
        }
        return i;
    }
}

思路二:一次遍歷(最優解)

上圖來自程序員小吳的題解
其實一個圖就可以看明白思路了。
在這裏插入圖片描述

 最優解:使用雙指針,p與q
其實就想要q到達null的時候,p距離q爲n+1(如果算上null的話),這樣p指向的下一個節點就是倒數第n個節點了
初始的時候,我們讓p和q都處於一個新的節點處,這個節點的next指向head
先讓q向後移動n+1位,這樣讓p和q之間保持n+1的距離,之後在共同移動,直到q爲null。

代碼二

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        //最優解:使用雙指針,p與q
        //其實就想要q到達null的時候,p距離q爲n,這樣p指向的下一個節點就是倒數第n個節點了
        //初始的時候,我們讓p和q都處於一個新的節點處,這個節點的next指向head
        //先讓q向後移動n+1位,之後共同移動,直到q爲null。
        ListNode p = new ListNode(-1);
        p.next = head;
        ListNode q = p;
        ListNode res = p;
        while(n+1>0)
        {
            q = q.next;
            n--;
        }
        while(q!=null)
        {
            p = p.next;
            q = q.next;
        }
        //已經找到倒數第n個節點,p爲倒數第n個節點前面的節點
        p.next = p.next.next;
        return res.next;
    }
}

7.兩兩交換鏈表中的節點

思路

其實就是兩兩交換鏈表中的節點,就注意這裏要判斷的和以往的不同,要判斷head不爲空同時head.next也不爲空,這樣才能交換head和head.next。還需要注意的就是pre節點要指向一個啞結點哦。

代碼

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head==null)
            return null;
        ListNode pre = new ListNode(-1);
        pre.next = head;
        ListNode res = pre;
        //防止剩餘單個,所以要判斷當前以及當前的下一個是否都不爲空。
        while(head!=null&&head.next!=null){
            ListNode secondnext = head.next.next;
            ListNode second = head.next;
            head.next = secondnext;
            second.next = head;
            pre.next = second;
            pre = head;
            head = head.next;
        }
        return res.next;
    }
}

8.奇偶鏈表

思想

把奇數節點放到一個鏈表中,把偶數節點放到另一個鏈表,到時候讓奇數的尾指向偶數的頭即可

需要奇數頭結點,尾結點,偶數頭結點與偶數尾結點
奇數頭結點和偶數頭結點都需要指向一個新的節點,同時指向的節點不同,但兩個節點的next都指向head。

代碼

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode oddEvenList(ListNode head) {
        if(head==null)
            return null;
        //把奇數放到一個鏈表中,把偶數放到另一個鏈表,到時候讓奇數的尾指向偶數的頭即可
        //需要奇數頭結點,尾結點,偶數頭結點與偶數尾結點
        //奇數頭結點和偶數頭結點都需要指向一個新的節點,同時指向的節點不同,但兩個節點的next都指向head。
        ListNode pre1 = new ListNode(-1);
        ListNode pre2 = new ListNode(-2);
        pre1.next = head;
        pre2.next = head;
        ListNode oddpre = pre1;
        ListNode evenpre = pre2;
        ListNode oddwei = pre1;
        ListNode evenwei = pre2;
        ListNode res = pre1;
        int i =1;
        while(head!=null){
            if(i%2!=0){
                oddwei.next = head;
                oddwei = oddwei.next; 
            }
            else{
                evenwei.next = head;
                evenwei = evenwei.next;
            }
            ListNode xiayige = head.next;
            head.next = null;
            head = xiayige;
            i++;
        }
        //這裏有可能偶數節點沒有做操作,所以要區分一下(只有一個節點的時候,就不需要鏈接了)
        if(oddwei!=evenpre.next)
            oddwei.next = evenpre.next;
        return res.next;  
    }
}

一些模板與總結

1.找鏈表中點

快慢指針一起走,結束後慢指針指向的就是中點。值得一提的是,如果鏈表長度爲奇數,slow會指向中間元素,如果長度爲偶數,則會指向中間兩個值中的左邊值。

  ListNode fast = head;
        ListNode slow = head;
        while(fast.next!=null&&fast.next.next!=null){
            fast = fast.next.next;
            slow = slow.next;
        }

2.反轉鏈表

  public ListNode reverseList(ListNode head) {
        ListNode pre = null;
        while(head!=null){
            ListNode next = head.next;
            head.next = pre;
            pre = head;
            if(next==null)
                break;
            head = next;
        }
        return head;
    }

3.前節點

前節點要麼是一個新的節點,然後新節點的next指向head,以便防止對鏈表的第一個節點有影響,要麼就是一個null。

ListNode pre = new ListNode(-1);
pre.next = head;

要麼就是空,方便翻轉鏈表。

ListNode pre = null;

4.思路

基本要麼直接就能求解,要麼就需要反轉一半鏈表,要麼就是用雙指針能有一些規律,最好多用筆畫一畫,鏈表的題目總涉及交換節點位置之類的,一不小心就會出現問題,因此切記用筆寫一寫。

對鏈表進行排序

1.合併兩個有序鏈表

思路

很簡單的題,使用歸併排序就好,同時比較兩個鏈表的當前值,誰比較小就把誰插入到新鏈表中,直至有一個鏈表長度爲0。之後還需要兩個判斷,單獨判斷l1鏈表是否爲空以及單獨判斷l2鏈表是否爲空,因爲有可能上一次判斷中有鏈表還剩餘值,直接把這個鏈表剩餘的值都插入到新鏈表中即可。

代碼

可能有人會問,不需要考慮一些特殊情況麼,比如兩個ListNode都爲空,如果這種情況下,就會直接走到return result.next,也就是返回null。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode node = new ListNode(-1);
        ListNode result = node;
        //第一次判斷
        while(l1!=null&&l2!=null){
            int left = l1.val;
            int right = l2.val;
            if(left<right){
                node.next = l1;
                l1 = l1.next;
            }else{
                node.next = l2;
                l2 = l2.next;
            }
            node = node.next;
        }
        //第二次判斷
        if(l1!=null){
            node.next = l1;
        }
        if(l2!=null)
        {
            node.next = l2;
        }
        return result.next;
    }
}

2.排序鏈表

思路

這裏偷一張圖,這道題的思路分爲三部分:(1)找中點,(2)分割,(3)合併。
(1)找中點,找中點是通過快慢指針來找到中間節點,當快指針到終點或無法跳兩格,慢指針就找到了中間節點
(2)分割,將slow後面的斷開,並將前後段繼續帶入函數繼續對兩段進行中斷分割…
分割結束的條件是,當前鏈表只剩下一個節點,就直接返回這個節點即可。
(3)合併,當我們把鏈表{2,3}分開之後,會分別返回鏈表:{2}與鏈表:{3},我們需要將這個鏈表合併,合併後將結果返回上一層遞歸,以便到上一層繼續合併。合併就是上一道題的歸併合併算法。
例子:我們以3,2,4,6舉例,遞歸(1)首先找中點,找到中點之後,將鏈表分爲兩個鏈表{3,2},以及{4,6}並分別代入遞歸(2)與遞歸(3),等待兩個遞歸的結果並將兩個鏈表合併;遞歸(2)首先找到中點,將鏈表分爲{3}(遞歸4),{2}(遞歸5);遞歸(4)直接返回{3},遞歸(5)直接返回{2};回到遞歸(2),將兩個鏈表進行合併,合併爲{2,3},這就是遞歸(2)的返回值;繼續走遞歸(3),同理,之後遞歸(3)會返回{4,6};回到遞歸(1),將{2,3}與{4,6}合併,就是{2,3,4,6}也就是我們的結果。
在這裏插入圖片描述

代碼

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode sortList(ListNode head) {
        /*
        1.快慢指針找到中間節點(當快指針到終點或無法跳兩格,慢指針就找到了中間節點)
        2.斷開成兩段4-2,1-3
        3.再斷開爲4,2;1,3
        4.合併鏈表:4和2;1和3
        5.合併鏈表:(2,4與1,3)使用歸併排序
        */
        if(head==null)
            return null;
        //只剩一個就直接返回吧(比如當前傳入的鏈表中只剩下4自己)
        if(head.next==null)
            return head;
        ListNode fast = head;
        ListNode slow = head;
        while(fast.next!=null&&fast.next.next!=null)
        {
            fast = fast.next.next;
            slow = slow.next;
        }
        //斷開兩段
        ListNode slowright = slow.next;
        slow.next = null;
        ListNode left = sortList(head);
        ListNode right = sortList(slowright);
        ListNode result = new ListNode(-1);
        ListNode res = result;
        //合併
        while(left!=null&&right!=null)
        {
            int nowleft = left.val;
            int nowright  = right.val;
            if(nowleft<nowright){
                result.next = left;
                left = left.next;
            }
            else{
                result.next = right;
                right = right.next;
            }
            result = result.next;
        }
        if(left!=null){
            result.next = left;
        }
        if(right!=null){
            result.next = right;
        }
        return res.next;
    }
    
}

3.對鏈表進行插入排序

思路

其實思路很簡單,但是代碼寫起來容易出現bug,先說下思路吧。插入排序就是將一個值插入到我們已經排序好的鏈表中。首先我們將這個值與最大值去比較,如果比最大值大,那麼就放到最大值後面即可,然後更新最大值;如果比最大值小,那麼就需要找他究竟要插入到哪裏去,對於鏈表來說插入比較方便,將比他小的最大值節點找到即可,將這個節點與當前節點相連,當前節點與這個節點的後面相連就完成了。
以上敘述大致可以分爲以下幾個步驟

 1.需要一個指針max指向當前已排序的最大值
 2.當前指針對應的值如果大於最大值就直接插入即可,否則就進入內部循環
 3.進入內部循環,可以使用一個指針pre,從頭向後找,當找到比now小的最大node,
 就可以讓node的next指向now,now的next指向node.next即可。
 注意事項:
 1.我們如何找到比now小的最大node呢,可以判斷while(pre.next.val<now.val)。這樣結束循環時的pre就是比now小的最大node。
 2.我們定義的節點(pre,max)不能是個null,要是個有具體值的節點,因爲當我們鏈表中已經有4後,想繼續加進來2,
 我們需要讓2加到4前面,如果我們定義的節點都是null就會在第一次判斷head的時候,都賦值爲head(null不能設next)。
 這樣就出現了問題,pre是4,就沒辦法把2添加到4前面。pre應該是指向一個不存在於head鏈表中的值,
 如果pre.next第一次就比當前的now節點大,就應該直接把now插入到pre與pre.next之間。
 3.我們設置一個ListNode res ,並讓pre和max都指向他,我們讓now就是head,最後返回的是res.next。

代碼

這其中有個巨大的坑,就是

 //爲什麼一定要加上這句呢?
max.next = headnext;

爲什麼要加上這句呢,還是以4,2,1,3舉例,當我們head走到2的時候,此時鏈表爲:

res->4->2->1->3
pre->res
max->4
head->2

進行判斷後,我們要將2放到res與4之間,

(headnext爲1)
ListNode headnext = head.next;
 //找到一個比now小的node,就可以讓node的next指向now,now的next指向node.next即可。
head.next = pre.next;
pre.next = head;
(pre歸位)
pre = res;
(讓head爲1)
head = headnext;

看起來代碼沒什麼問題,但你用手來畫一下就會發現這裏只是將res連接到2,2連接到4,4連接到2這條線根本沒有斷開!
所以當我們最後返回結果的時候就無法返回,因爲鏈表出現了環。所以我們要是用max.next = headnext,來將4連接到2這條線斷開,也就是將max與head的線斷開,之前我們考慮的都是head後面的要斷開,以及pre和pre後面的要斷開,可是沒有考慮前面連着head的也需要斷開。

完整代碼

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode insertionSortList(ListNode head) {
        /*插入排序
        1.需要一個指針指向當前已排序的最大值
        2.當前指針對應的值如果大於最大值就直接插入即可,否則就進入內部循環
        3.進入內部循環,可以使用一個指針,從頭向後找,當找到一個比now小的node,就可以讓node的next指向now,now的next指向node.next即可。
        */
        ListNode res =new ListNode(Integer.MIN_VALUE);
        ListNode pre= res;
        ListNode max=res;
        while(head!=null){
           if(max.val<head.val){
               max.next = head;
               max = head;
               head = head.next;
               //max = head;
           }
            else{
                
                while(pre.next.val<head.val){
                        pre = pre.next;
                    }
                    ListNode headnext = head.next;
                    //爲什麼一定要加上這句呢?
                   
                    max.next = headnext;
                    //找到一個比now小的node,就可以讓node的next指向now,now的next指向node.next即可。
                    head.next = pre.next;
                    pre.next = head;
                    pre = res;
                    head = headnext;
                }
           }
        return pre.next;

        }
    }

4.合併K個排序鏈表

第一種算法

對2個鏈表合併K-1次,也就是先將第一個和第二個合併,然後將合併的結果在與第三個鏈表合併,相當於2個鏈表合併了K-1次。
合併的算法就是歸併排序。

代碼

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        //先嚐試合併2個鏈表k-1次(合併第一個與第二個,然後用他們合併好的和第三個合併,直到最後一個)
        if(lists.length==0)
            return null;
        ListNode res = lists[0];
        for(int i=0;i<lists.length-1;i++)
        {
           ListNode node = merge(res,lists[i+1]);
           res = node;
        }
        return res;
    }
    public ListNode merge(ListNode node1,ListNode node2){
        ListNode res = new ListNode(-1);
        ListNode head = res;
        while(node1!=null&&node2!=null){
            int val1 = node1.val;
            int val2 = node2.val;
            if(val1<val2){
                res.next = node1;
                node1 =node1.next;
            }else{
                res.next = node2;
                node2 = node2.next;
            }
            res = res.next;
        }
        if(node1!=null){
            res.next = node1;
        }
        if(node2!=null){
            res.next = node2;
        }
        return head.next;
    }

}

第二種算法

使用分治算法,上一種算法會導致一些節點多次重複判斷,比如list0中的節點,以下圖爲例,list0中的節點如果以上面的算法就會比較5次,與每個list中的節點都要比較一次,而分治算法,就只比較三次,比較的複雜度由N,變成了log2N,在乘上鍊表的個數就是O(k*log2N)在這裏插入圖片描述

代碼

我們上面的思路用來理解思想,真實的方式是使用如下方法去解決,我們不合並左右的,我們去合併第一個與最後一個,第二個與倒數第二個,以此類推。
在這裏插入圖片描述
代碼需要注意的就是奇數偶數的情況,可以稍微用例子試一試,很容易寫錯,注意外層循環的次數爲(list.length+1)/2次循環,而不是list.length/2次循環,因爲如果長度爲3的話,要進行兩次循環,而非3/2=1,一次循環。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        //分治算法,兩個兩個去歸併
        if(lists.length==0)
            return null;
        int times = lists.length;
        //循環lists.length/2次(可以以中間分開對半合併,第一個與最後一個,第二個與倒數第二個,然後繼續折半)
        //剩餘長度至少爲2纔可以做
        while(times>1){
            //奇數的話比如times爲5,就只需要比較0,4;1,3,而2不需要,因此是times/2=2次
            //但是如果是偶數4的話,也需要比較0,3;1,2,同樣爲times/2=2次
            for(int i=0;i<times/2;i++){
                lists[i] = merge(lists[i],lists[times-1-i]);
            }
            //而到了這裏,如果奇數的話,5/2就是2了其實是三個,因此應該是(times+1)/2,,而4個的話,(4+1)/2也是2。
            times = (times+1)/2;
        }
        return lists[0];
    }
    public ListNode merge(ListNode node1,ListNode node2){
        ListNode res = new ListNode(-1);
        ListNode head = res;
        while(node1!=null&&node2!=null){
            int val1 = node1.val;
            int val2 = node2.val;
            if(val1<val2){
                res.next = node1;
                node1 =node1.next;
            }else{
                res.next = node2;
                node2 = node2.next;
            }
            res = res.next;
        }
        if(node1!=null){
            res.next = node1;
        }
        if(node2!=null){
            res.next = node2;
        }
        return head.next;
    }

}

反轉鏈表

1.反轉鏈表 II

2.重排鏈表

思路

比較簡單就不放思路了。

代碼

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public void reorderList(ListNode head) {
        if(head==null)
            return;
        //快慢指針找中間節點,然後翻轉中間節點之後的鏈表,並與前面的鏈表拼接。
        ListNode fast = head;
        ListNode slow = head;
        while(fast!=null&&fast.next!=null){
            fast = fast.next.next;
            slow = slow.next;
        }
        //斷開中間節點之後的鏈表
        ListNode second = slow.next;
        slow.next = null;
        //翻轉鏈表
        ListNode pre = null;
        
        while(second!=null){
            ListNode next = second.next;
            second.next = pre;
            pre = second;
            if(next!=null)
                second = next;
            else
                break;
        }
        
        
        /*連接第一段與翻轉的第二段
        第二段小於等於第一段的長度
        比如奇數長度:12345,第二段就是54
        偶數長度1234,第二段就是43
        所以第二段不指向空即可
        */
       
        while(second!=null){
            ListNode onenext = head.next;
            ListNode twonext = second.next;
            head.next = second;
            second.next = onenext;
            head = onenext;
            second = twonext;
        }
        return ;
        
    }
}

3.K 個一組翻轉鏈表

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章