链表题汇总

链表和算法简直不是一个东西,纵然做了很多算法题,没有专门训练过链表还是很容易成为炮灰的,用几天时间把力扣上的链表题刷了记录一下。

主要操作有快慢指针、链表合并、拆分链表、重组链表、链表移位、链表成环、链表相交、链表反转、链表排序等,一般都是指针指来指去就够了,尽量不要用数组存储,否则体现不出水平,时常需要判空。

以下题目大致按难度排序

力扣876:链表的中间节点

思路:1.常规的遍历求长度,再跑到中点停下来 2.快慢指针,一个走一步,一个走两步,快指针跑到末尾时慢指针就在中点

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode middleNode(ListNode head) {
        ListNode fast=head,slow=head;
        while(fast!=null && fast.next!=null){
            slow=slow.next;
            fast=fast.next.next;
        }
        return slow;
    }
}
力扣876

 

剑指offer18:删除链表的节点

思路:遍历过程中,下一个节点需要删除的话,则把当前节点的指针指向下下个节点。特判头节点。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode deleteNode(ListNode head, int val) {
        if(head==null)
            return null;
        ListNode cur=head;
        //特判第一个节点
        if(head.val==val)
            return head.next;
        while(cur!=null){
            //下一个节点要删除,就直接指向 下下个节点,退出
            if(cur.next.val==val){
                cur.next=cur.next.next;
                break;
            }
            cur=cur.next;
        }
        return head;
    }
}
剑指offer18

 

剑指offer52:两个链表的第一个公共节点

思路1:计算链表长度差k,长的先跑k步,短的再开始跑,会在交点相遇,若无交点,最后同时走到null。

/**
 * 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==headB)
            return headA;
        int len1=size(headA),len2=size(headB);
        //保证A是短的,B是长的
        if(len1>len2){
            ListNode temp=headA;
            headA=headB;
            headB=temp;
        }
        int k=Math.abs(len1-len2);
        ListNode cur1=headA,cur2=headB;
        //长的链表B先走k步
        for(int i=0;i<k;i++){
            cur2=cur2.next;
        }
        while(cur1!=cur2){
            cur1=cur1.next;
            cur2=cur2.next;
        }
        return cur1;
    }

    //获取链表长度
    public static int size(ListNode head){
        int res=0;
        while(head!=null){
            res++;
            head=head.next;
        }
        return res;
    }
}
剑指offer52

思路2:一起跑,跑完换线跑,有交点会相遇,没交点最后也都是跑完全程同时到null,不会死循环。

/**
 * 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) {
        ListNode cur1=headA,cur2=headB;
        while(cur1!=cur2){
            cur1=cur1==null?headB:cur1.next;
            cur2=cur2==null?headA:cur2.next;
        }
        return cur1;
    }
}
剑指offer52

 

力扣141:环形链表(腾讯面试)

思路:快慢指针,如果有环的话,不会遇到null;一快一慢跑进环里总会相遇,一旦遇到null就可以退出。注意判空避免空指针异常。

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head==null)
            return false;
        ListNode cur1=head,cur2=head.next;
        while(cur1!=null && cur2!=null){
            if(cur1==cur2){
                return true;
            }
            cur1=cur1.next;
            if(cur2.next!=null)
                cur2=cur2.next.next;
            else 
                break;
        }
        return false;
    }
}
力扣141

 

面试题02.05:链表求和

思路:逆序存储,从首位开始往后走,注意三个点,进位、一条链表先为空、走完全程还有进位。

/**
 * 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 cur1=l1,cur2=l2,ans=new ListNode(0),res=ans,last=ans;
        int x=0,sum=0;//进位
        while( cur1!=null || cur2!=null || x!=0){
            sum=x;
            if(cur1!=null && cur2!=null){
                sum=cur1.val+cur2.val+x;
                cur1=cur1.next;
                cur2=cur2.next;
            }else if(cur1!=null && cur2==null){
                sum=cur1.val+x;
                cur1=cur1.next;
            }else if(cur1==null && cur2!=null){
                sum=cur2.val+x;
                cur2=cur2.next;
            }
            x=sum/10;
            last=ans;
            ans.val=sum%10;
            ans.next=new ListNode(0);
            ans=ans.next;
        }
        last.next=null;
        return res;
    }
}
面试题02.05

 

力扣61:旋转链表

思路:移动数位对长度求模,看移位把链表拆成两条,记得断开第一条链表末尾,尾再接上头。

/**
 * 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 rotateRight(ListNode head, int k) {
        if(head==null)
            return null;
        int len=size(head);
        k=k%len;
        if(k==0 || head.next==null)
            return head;
        ListNode cur=head;
        for(int i=1;i<(len-k);i++){
            cur=cur.next;
        }
        ListNode temp=cur.next;//后面部分链表起点,不会是null
        cur.next=null;//前面部分末尾置空
        cur=temp;
        //找到末尾节点
        while(cur.next!=null){
            cur=cur.next;
        }
        //末尾节点->头节点
        cur.next=head;
        return temp;
    }

    public static int size(ListNode head){
        int res=0;
        ListNode cur=head;
        while(cur!=null){
            res++;
            cur=cur.next;
        }
        return res;
    }

}
力扣61

 

力扣817:链表组件

思路:标记一下G中的数,遍历一次链表,对连续存在于G的数区间计数,累计到答案中。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public int numComponents(ListNode head, int[] G) {
        //最终答案  连续存在于G的数的数量
        int ans=0,num=0;
        //对G中的数做一下标记
        HashMap<Integer,Integer> map=new HashMap<>();
        for(int i=0;i<G.length;i++)
            map.put(G[i],1);
        ListNode cur=head;
        while(cur!=null){
            if( map.get(cur.val)!=null ){
                num++;
            }else {
                if(num!=0)
                    ans++;
                num=0;
            }
            cur=cur.next;
        }
        if(num!=0)
            ans++;
        return ans;
    }
}
力扣817

 

剑指offer24:反转链表

思路:从头走到尾,将当前结点指向上一个结点。

实现:上一个结点用last记录,当前结点用cur记录。下一个结点用t临时存储,因为修改了当前结点的指向就失去了下一个结点的位置,需要存储。

/**
 * 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)
            return null;
        ListNode cur=head;
        ListNode t,last=null;
        while(cur != null){//当前不为空,把当前指向上一个
            t=cur.next;//临时存储下一个结点
            cur.next=last;//当前结点指向上一个结点
            last=cur;//更新下一轮的【上一个结点】为【当前结点】
            if(t==null)
                break;
            cur=t;//更新下一轮的【当前结点】为【下一个结点】
        }
        return cur;
    }
}
剑指offer24

 

力扣92:反转链表II

思路:将链表分为3段,第1段正常,第2段反转,第3段正常。注意第1段和第3段为空的情况。详看代码,附图解。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseBetween(ListNode head, int m, int n) {
        if(head==null || head.next==null)
            return head;
        ListNode temp=new ListNode(-1),cur=head;
        //第1段为空 则 直接返回第2第3段链表
        if(m==1)
            return reverse(head,n-m+1);
        temp.next=head;
        int i=1;
        //止步于 第2段链表前
        while(i<(m-1)){
            cur=cur.next;
            i++;
        }
        cur.next=reverse(cur.next,n-m+1);
        return temp.next;
    }

    public static ListNode reverse(ListNode head,int x){
        ListNode tail=head,cur=head,last=null,temp;
        while(x-->0){
            temp=cur.next;//临时存储下一个节点
            cur.next=last;//当前节点指向上一个节点
            last=cur;//更新上一个节点,准备下一轮
            cur=temp;//移动当前节点
        }
        /*
        第3段链表不为空
               tail        last  cur
        null  ←  0  ←  0  ←  0    0  →  0  →  0

        第3段链表为空
               tail        last  cur
        null  ←  0  ←  0  ←  0   null
        */
        tail.next=cur;
        return last;
    }
}
力扣92

 

剑指offer35:复杂链表的复制

题意:链表多出一个随即指针随机指向,要求复制出一模一样的链表。

思路:主流解法都是在原链表中插入复制链表,最后拆分链表。需要保持原链表的内存地址不变,而不是单单分离出复制链表。

/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/
class Solution {
    public Node copyRandomList(Node head) {
        if(head==null)
            return null;
        //1.原链表的每个节点后插入复制节点
        Node cur=head;//
        while(cur!=null){
            //复制出当前节点
            Node temp=new Node(cur.val);
            //复制点插入cur和cur.next之间
            temp.next=cur.next;
            cur.next=temp;
            //移动当前节点
            cur=temp.next;
        }

        //2.处理random指针
        cur=head;
        while(cur!=null){
            if( cur.random!=null )
                cur.next.random=cur.random.next;//复制点->random的复制点
            cur=cur.next.next;
        }

        //3.分离链表
        cur=head.next;//第一个复制点
        Node res=head.next,pre=head;
        while(cur!=null){
            pre.next=cur.next;
            pre=cur.next;
            if(pre==null){//特判末尾节点
                cur.next=null;
                break;
            }
            cur.next=pre.next;
            cur=pre.next;
        }
        return res;
    }
}
剑指offer35

 

力扣143:重排链表(招银面试)

题意:L0→L1→L2→L3→L4...→Ln-1→Ln 变成  L0→Ln→L1→Ln-1→L2→Ln-2...

思路:1.数组存储再双指针从新组成 2.快慢指针找中点,拆分成两条链表,第二条反转,再合并;融合几个简单操作。

/**
 * 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 void reorderList(ListNode head) {
        if(head==null || head.next==null)
            return;
        ListNode mid=mid(head);
        ListNode cur=head;
        //断开链表
        while(cur!=null){
            if(cur.next==mid)
                cur.next=null;
            cur=cur.next;
        }
        mid=reverse(mid);//反转后半部分链表
        merge(head,mid);
    }

    //获取链表中点
    public static ListNode mid(ListNode head){
        //快慢指针
        ListNode fast=head,slow=head;
        while(fast!=null && fast.next!=null){
            slow=slow.next;
            fast=fast.next.next;
        }
        return slow;
    }

    //反转链表
    public static ListNode reverse(ListNode head){
        if(head==null)
            return null;
        ListNode cur=head,t,last=null;
        while(cur != null){//当前不为空,把当前指向上一个
            t=cur.next;//临时存储下一个结点
            cur.next=last;//当前结点指向上一个结点
            last=cur;//更新下一轮的【上一个结点】为【当前结点】
            if(t==null)
                break;
            cur=t;//更新下一轮的【当前结点】为【下一个结点】
        }
        return cur;
    }

    //交替合并链表
    public static ListNode merge(ListNode l1,ListNode l2){
        ListNode cur1=l1,cur2=l2,temp1,temp2;
        while(cur1!=null && cur2!=null){
            temp1=cur1.next;
            cur1.next=cur2;
            temp2=cur2.next;
            //l1.size()<=l2.size(),防止丢失l2最后一个
            if(temp1==null)
                break;
            cur2.next=temp1;
            cur1=temp1;
            cur2=temp2;
        }
        cur1.next=cur2;
        return l1;
    }
}
力扣143

 

力扣86:分隔链表

思路:样例看了很久才看明白什么意思,小于x的放前面,大于等于x的放后面,在初始链表中的相对位置不变。小于x的节点按顺序放在一条链表里,大于等于x的节点按顺序放在另一条链表里,最后把两条链表接起来。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode partition(ListNode head, int x) {
        ListNode l1=new ListNode(-1),l2=new ListNode(-1),cur=head;
        ListNode res1=l1,res2=l2;
        while(cur!=null){
            if(cur.val<x){
                l1.next=cur;
                l1=l1.next;
            }else{
                l2.next=cur;
                l2=l2.next;
            }
            cur=cur.next;
        }
        l2.next=null;
        l1.next=res2.next;
        return res1.next;
    }
}
力扣86

 

力扣725:分隔链表

思路:均分长度取整,多出的节点分给前面的链表,人均一个。截断链表时注意记录下一条链表的起点,节点若不够分则后面的链表为空,需要判空。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode[] splitListToParts(ListNode root, int k) {

        ListNode[] ans=new ListNode[k];
        if(root==null)
            return ans;
        int len=0;
        ListNode cur=root;
        while(cur!=null){
            len++;
            cur=cur.next;
        }
        //均分之后的余数,注定前r个链表多1个节点,x是均分节点数
        int r=len%k,x=len/k;
        //第i个链表 当前链表长度
        int i=0,num=0;
        cur=root;
        ListNode start,temp=root;
        //获取完最后一个链表没有对cur置空,需要用i<k退出
        while( cur!=null && i<k){
            //r个较长的链表不会必定遇到空的情况
            if(i<r){
                start=temp;
                cur=start;
                for(int j=1;j<(1+x);j++){
                    cur=cur.next;
                }
                temp=cur.next;//保存下一个起点
                cur.next=null;//截断链表尾
                ans[i]=start;//保存每段链表头
            }else{
                start=temp;
                cur=start;
                for(int j=1;j<x && cur!=null;j++)
                    cur=cur.next;
                //若遇到空说明链表已经被拆完了,直接退出
                if(cur==null){
                    ans[i]=start;
                    break;
                }
                temp=cur.next;
                cur.next=null;
                ans[i]=start;
            }
            i++;
        }
        return ans;
    }
}
力扣725

 

力扣148:排序链表

思路:归并排序,找中点分割+合并。空间复杂度不符合题目要求。但就是不想看迭代,遇到了算我倒霉。

/**
 * 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 sortList(ListNode head) {
        return segment(head);
    }

    //归并排序,分割一条链表为两个链表再合并成一条链表
    public static ListNode segment(ListNode head){
        if(head==null)
            return null;
        if(head.next==null)
            return head;
        //快慢指针找中点
        ListNode slow=head,fast=head.next;
        while(fast!=null && fast.next!=null){
            slow=slow.next;
            fast=fast.next.next;
        }
        ListNode l2=segment(slow.next);
        slow.next=null;//可能并非均分,将就着断
        ListNode l1=segment(head);
        return merge(l1,l2);
    }

    //合并有序链表
    public static ListNode merge(ListNode l1,ListNode l2){
        ListNode res=new ListNode(0);
        ListNode cur1=l1,cur2=l2,now=res;
        while(cur1!=null && cur2!=null){
            if(cur1.val<cur2.val){
                now.next=cur1;
                cur1=cur1.next;
            }else{
                now.next=cur2;
                cur2=cur2.next;
            }
            now=now.next;
        }
        //谁没跑完就把它接到尾巴
        now.next=cur2==null?cur1:cur2;
        return res.next;
    }
}
力扣148

 

力扣234:回文链表

思路:找中点分割,第二条反转,一起从头遍历看值是否相同。注意空链表返回true。

/**
 * 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 || head.next==null)
            return true;
        ListNode mid=mid(head);
        ListNode cur=head;
        //断开链表
        while(cur!=null){
            if(cur.next==mid)
                cur.next=null;
            cur=cur.next;
        }
        mid=reverse(mid);
        ListNode cur1=head,cur2=mid;
        //cur1.size()<=cur2.size()
        while(cur1!=null){
            if(cur1.val!=cur2.val)
                return false;
            cur1=cur1.next;
            cur2=cur2.next;
        }
        return true;
    }

    //获取链表中点
    public static ListNode mid(ListNode head){
        //快慢指针
        ListNode fast=head,slow=head;
        while(fast!=null && fast.next!=null){
            slow=slow.next;
            fast=fast.next.next;
        }
        return slow;
    }

    //反转链表
    public static ListNode reverse(ListNode head){
        if(head==null)
            return null;
        ListNode cur=head,t,last=null;
        while(cur != null){//当前不为空,把当前指向上一个
            t=cur.next;//临时存储下一个结点
            cur.next=last;//当前结点指向上一个结点
            last=cur;//更新下一轮的【上一个结点】为【当前结点】
            if(t==null)
                break;
            cur=t;//更新下一轮的【当前结点】为【下一个结点】
        }
        return cur;
    }

}
力扣234

 

力扣382:链表随机节点

思路:蓄水池抽样算法

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {

    /** @param head The linked list's head.
        Note that the head is guaranteed to be not null, so it contains at least one node. */
    static ListNode head;
    public Solution(ListNode head) {
        this.head=head;
    }
    
    /** Returns a random node's value. */
    public int getRandom() {
        int cnt=0,ans=0;
        Random r=new Random();
        ListNode cur=head;
        while(cur!=null){
            cnt++;
            if(r.nextInt()%cnt==0)
                ans=cur.val;
            cur=cur.next;
        }
        return ans;

    }
}

/**
 * Your Solution object will be instantiated and called as such:
 * Solution obj = new Solution(head);
 * int param_1 = obj.getRandom();
 */
力扣382

 

力扣328:奇偶链表(映客笔试)

思路:分别用两条链表存储奇偶节点,最后拼接,注意偶链表末尾置空,记录偶链表头。

/**
 * 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 oddEvenList(ListNode head) {
        if(head==null || head.next==null)
            return head;
        ListNode cur1=head,cur2=head.next,cur=head.next.next;
        ListNode temp=head.next;//防止丢失偶数链表的头部
        int cnt=1;
        while(cur!=null){
            if(cnt%2==1){//奇数
                cur1.next=cur;
                cur1=cur1.next;
            }else{
                cur2.next=cur;
                cur2=cur2.next;
            }
            cur=cur.next;
            cnt++;
        }
        cur1.next=temp;
        cur2.next=null;
        cur=head;
        return head;
    }
}
力扣328

 

力扣1290:二进制链表转整数

思路:反转,遍历,累计val*2的幂次方。

class Solution {
    static int[] two=new int[30];
    public int getDecimalValue(ListNode head) {
        if(head==null)
            return 0;
        two[0]=1;
        for(int i=1;i<30;i++)
            two[i]=two[i-1]*2;
        head=reverse(head);
        ListNode cur=head;
        int i=0,ans=0;
        while(cur!=null){
            ans+=two[i++]*cur.val;
            cur=cur.next;
        }
        return ans;
    }

    //反转链表
    public static ListNode reverse(ListNode head){
        if(head==null)
            return null;
        ListNode cur=head,t,last=null;
        while(cur != null){//当前不为空,把当前指向上一个
            t=cur.next;//临时存储下一个结点
            cur.next=last;//当前结点指向上一个结点
            last=cur;//更新下一轮的【上一个结点】为【当前结点】
            if(t==null)
                break;
            cur=t;//更新下一轮的【当前结点】为【下一个结点】
        }
        return cur;
    }
}
力扣1290

 

面试题04.03:特定深度节点链表

思路:bfs过程中,每次把一层的节点弄成链表,先放在ArrayList里,最后再用toArray转数组。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode[] listOfDepth(TreeNode tree) {
        if(tree==null)  
            return null;
        LinkedList<TreeNode> list=new LinkedList<>();
        ArrayList<ListNode> res = new ArrayList<>();
        list.add(tree);
        ListNode temp=new ListNode(-1),cur;//伪节点头
        while(list.size()>0){
            cur=temp;
            //获取当前层的节点数,拼接一条链表
            int len=list.size();
            for(int i=0;i<len;i++){
                TreeNode now=list.poll();
                cur.next=new ListNode(now.val);
                cur=cur.next;
                //略过空的
                if(now.left!=null)
                    list.add(now.left);
                if(now.right!=null)
                    list.add(now.right);
            }
            res.add(temp.next);
        }
        return res.toArray(new ListNode[]{});//ArrayList转数组
    }
}
面试题04.03

 

 

力扣23:合并K个升序链表(映客笔试)

思路1:优先队列

/**
 * 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 mergeKLists(ListNode[] lists) {
        //优先队列
        Comparator<ListNode> comparator=new Comparator<ListNode>() {
            public int compare(ListNode o1, ListNode o2) {
                return o1.val<o2.val?-1:1;
            }
        };
        Queue<ListNode> que=new PriorityQueue<ListNode>(comparator);
        ListNode temp,cur;
        for(int i=0;i<lists.length;i++){
            cur=lists[i];
            while(cur!=null){
                que.add(cur);
                temp=cur;
                cur=cur.next;
                temp.next=null;//每个节点的next都置空,防止成环
            }
        }
        ListNode head=new ListNode(-1);
        cur=head;
        while(que.size()>0){
            cur.next=que.poll();
            cur=cur.next;
        }
        return head.next;
    }
}
优先队列

思路2:归并排序

class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        if(lists.length==0)
            return null;
        return sort(0,lists.length-1,lists);
    }

    public static ListNode sort(int l,int r,ListNode[] lists){
        if(l==r)
            return lists[l];
        int mid=(l+r)/2;
        ListNode l1=sort(l,mid,lists);
        ListNode l2=sort(mid+1,r,lists);
        return merge(l1,l2);
    }

    //合并有序链表
    public static ListNode merge(ListNode l1,ListNode l2){
        ListNode res=new ListNode(0);
        ListNode cur1=l1,cur2=l2,now=res;
        while(cur1!=null && cur2!=null){
            if(cur1.val<cur2.val){
                now.next=cur1;
                cur1=cur1.next;
            }else{
                now.next=cur2;
                cur2=cur2.next;
            }
            now=now.next;
        }
        //谁没跑完就把它接到尾巴
        now.next=cur2==null?cur1:cur2;
        return res.next;
    }
}
归并排序

 

力扣147:对链表进行插入排序

思路:遍历过程中,维持前面部分有序,严格递增。当前节点需要调整位置时,从头遍历找到适当的插入位置(前面小于当前节点,后面大于当前节点)。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode insertionSortList(ListNode head) {
        if(head==null || head.next==null)
            return head;
        //伪头节点
        ListNode dummy=new ListNode(0);
        dummy.next=head;
        //pre为cur的前一个节点,通过cur=pre.next去移动即可
        ListNode pre=head,cur=head.next,temp; 
        while(cur!=null){
            if(pre.val<=cur.val){
                pre=pre.next;
            }else{
                temp=dummy;
                //找到最后一个位置比cur小的位置,保证递增严格
                while(temp.next.val<=cur.val){
                    temp=temp.next;
                }
                /*
                          temp   pre   cur
                1  →  2  →  4  →  8  →  5  →  6...
                */
                pre.next=cur.next;
                cur.next=temp.next;
                temp.next=cur;
                /*
                          temp   cur   pre
                1  →  2  →  4  →  5  →  8  →  6...
                */
            }
            cur=pre.next;
        }
        return dummy.next;
    }
}
力扣147

 

力扣25:K个一组翻转链表

思路:对每一小段需要翻转k个节点的子链表,需要子链表的头节点,子链表的前驱节点,最后返回子链表的尾节点当作新的前驱节点。

/**
 * 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 reverseKGroup(ListNode head, int k) {
        ListNode dummy=new ListNode(-1),cur=head;
        dummy.next=head;
        int len=0;
        while(cur!=null){
            cur=cur.next;
            len++;
        }
        cur=dummy;
        int t=len/k;//反转t段长度为k的链表
        while(t-->0){
            cur=reverseK(cur,cur.next,k);
        }
        cur=dummy.next;
        return dummy.next;
    }

    //对后续k个节点进行反转
    public static ListNode reverseK(ListNode pre,ListNode head,int k){
        if(head==null)
            return head;
        //反转时需要指向上一个节点,当前节点,临时存储下一个节点
        ListNode last=null,cur=head,temp;
        while(k-->0 && cur!=null){
            //整条链的前驱节点不断往后指,改变链表指向
            pre.next=cur;
            temp=cur.next;
            cur.next=last;
            last=pre.next;
            cur=temp;
        }
        //返回反转后的链表尾,当作下k个节点链表的前驱节点
        head.next=cur;
        return head;
    }
}
力扣25

 

力扣82. 删除排序链表中的重复元素 II

思路:last表示上一个节点,cur表示当前节点,next表示下一个节点。如果当前节点和下一个节点值相同,就在while循环里找到cur和next不同的节点,注意next直接跑到末尾;不是末尾则把cur跳过相同区域指到当前next,准备开始下一轮。造一个伪头节点作为初始的last。详看代码,动手模拟。

/** 82. 删除排序链表中的重复元素 II
     *  res 1 2 3 3 3 4 4 5 6 6 7 7 8
     *    l c n
     *  cur表示当前节点,last表示上一个节点,next表示下一个节点
     *  如果cur与下一个节点相同,就一直走到下一个不同的节点,用last连接起来,但last暂时不移位
     */
    public ListNode deleteDuplicates(ListNode head) {
        if(head==null || head.next==null)
            return head;
        ListNode res=new ListNode();
        res.next=head;
        //链表长度至少为2,此时next绝对不为null
        ListNode last=res,cur=head,next=head.next;
        while(cur!=null){
            next=cur.next;
            if(next==null){
                last.next=cur;
                break;
            }
            if(cur.val==next.val){
                while(cur.val==next.val){
                    next=next.next;
                    if(next==null){ //最后一段是连续的,直接设置末尾为空返回
                        last.next=null;
                        return res.next;
                    }
                }
                cur=next;
                continue;
            }
            last.next=cur;
            last=cur;
            cur=cur.next;
            if(cur==null){
                last.next=cur;
                break;
            }
        }
        return res.next;
    }
力扣82

 

 

写在最后,Java用容器很容易解出这些题,例如用ArrayList存起来后,翻转、找中点等操作用下标即可。笔试的时候可以骗分,实在不行写一下极端情况(head==null || head.next==null)返回head大概率也能骗点分,悔不当初;面试手撕用容器就落了下乘。做题需要留意的有这些变量,头节点head、伪头节点(头节点的前驱)dummy、遍历到的当前节点cur、当前节点的上一个节点last、临时存储下一个节点temp(翻转后无法通过cur=cur.next获取,先用temp存一下),以及各种情况下判空break,尽量有属于自己的命名风格以及修改操作习惯,看了题解也尝试用自己的风格写出来,在草稿纸上涂涂画画,以后遇到就不成问题了。别无他长,惟手熟尔。

posted @ 2020-11-23 12:08  守林鸟  阅读(269)  评论(0编辑  收藏  举报