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 个一组翻转链表

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