【JavaSE】链表小练习(第1弹)(反转链表、删除链表指定元素的所有节点、升序链表合并、指定数值分割链表、删除有序链表的重复节点)

目录

一、反转链表

方法一:头插法

方法二:递归法

二、删除指定元素的所有节点

方法一:尾插法

方法二:遍历修改

三、升序链表合并

四、指定数值分割链表

五、删除有序链表的重复节点

方法一:遍历法

方法二:递归法


一、反转链表

leetcode(206题)

方法一:头插法

        我们根据最基本的想法,将链表的每个节点按顺序头插在新的节点上。看代码

    public static ListNode reverse1(ListNode head){
        ListNode prev=null;
        ListNode cur=head;
        while(cur!=null){
            ListNode next=cur.next;

            cur.next=prev;
            prev=cur;
            
            cur=next;
        }
        return prev;
    }

        其实我们常用的用三个指针(引用),遍历整个链表来反转的方法,其本质上就是头插法。

方法二:递归法

        先看代码

    public static ListNode reverse3(ListNode head){
        //返回结束的条件
        if(head==null||head.next==null){
            return head;
        }
        //递归的部分
        ListNode newhead=reverse3(head.next);

        head.next.next=head;
        head.next=null;
        return newhead;
    }

递归的思想就是将原链表的下一个节点,再次馈送到递归函数中。例如1=>2=>3=>4=>5=>null这个链表,经过 ListNode newhead=reverse3(head.next);之后变成了null<=2<=3<=4<=5,同时head也指向1,1指向2。然后我们需要将2指向1,然后将1指向null,然后返回新的头就行了。也就是通过head.next.next=head;head.next=null;return newhead;完成链表的反转。

二、删除指定元素的所有节点

leetcode(203题)

方法一:尾插法

        思想就是遍历整个链表,如果该节点的值和给定值不同,就将该值尾插到一个新的链表之中。主要是尾插部分的实现。

        该尾插方法的不同之处在于,为了保证下次遍历,需要将每次插入的元素的指向改为null。

    public ListNode removeElements(ListNode head, int val){
        ListNode fakehead=null;
        ListNode cur=head;
        while(cur!=null){
            ListNode next=cur.next;
            if(cur.val!=val){
                fakehead=lastAdd(fakehead,cur);
            }
            cur=next;
        }
        return fakehead;
    }
    private static ListNode lastAdd(ListNode fakehead, ListNode cur) {
        if(fakehead==null){
            fakehead=cur;
            fakehead.next=null;
        }else{
            ListNode c=fakehead;
            while (c.next!=null){
                c=c.next;
            }
            c.next=cur;
            cur.next=null;
        }
        return fakehead;
    }

小技巧:假头法

使用假头法改进这个尾插法

    public ListNode removeElements(ListNode head, int val){
        ListNode fake=new ListNode(937854328);
        ListNode c=head;
        ListNode newlast=fake;
        while(c!=null){
            ListNode n=c.next;
            if(c.val!=val){
                newlast.next=c;
                c.next=null;
                newlast=c;
            }
            c=n;
        }
        return fake.next;
    }

方法二:遍历修改

遍历每一个元素,如果该节点的值等于给定值,将该节点的前一个节点指向下一个节点,如果是头节点则将头节点后移。该方法需要考虑的情况比较多,相对比较复杂容易弄混。

    public ListNode removeElements(ListNode head, int val) {
        ListNode newHead=head;
        ListNode prev=null;
        ListNode cur=head;
        while (cur!=null){
            if(cur.val==val){
                if(prev==null){
                    newHead=newHead.next;
                }else{
                    prev.next=cur.next;
                }
            }else{
                prev=cur;
            }
            cur=cur.next;
        }
        return newHead;
    }

小技巧:假头法 

        使用一个假头,这样链表的每个节点都有一个前置节点,并且就算链表为空加上假头后也不为空了。使用假头法来改进方法二,代码如下:

    public ListNode removeElements2(ListNode head, int val){
        ListNode fakehead=new ListNode(937854328);
        fakehead.next=head;
        ListNode prev=fakehead;
        ListNode cur=head;
        while(cur!=null){
            if(cur.val==val){
                prev.next=cur.next;
            }else{
                prev=cur;
            }
            cur=cur.next;
        }
        return fakehead.next;
    }

        我们对于假头法的使用需要分清楚情况,像这种情况下使用假头法就可以有效的简化问题。

三、升序链表合并

leetcode(21题)

方法 :首先我们需要想清楚,怎么合并两个升序的链表。我们可以遍历两个链表,将小节点的放到新链表上,然后将小的节点的链表向后移再进行比较,再将小的节点放在新链表的末尾然后再后移。直到一个链表为空,将不为空的链表接在新链表的尾节点上。同时我们需要考虑特殊情况。代码如下:

    public ListNode mergeTwoLists(ListNode list1, ListNode list2){
        ListNode newhead=null;//设定新链表的头
        ListNode c1=list1;
        ListNode c2=list2;
        ListNode newlast=null;//由于链表末尾元素是尾插进去的,我们可以用一个节点来记录末尾,这样就不用遍历找末尾

        if(c1==null){
            return c2;
        }
        if(c2==null){
            return c1;
        }
        while(c1!=null && c2!=null){
            if(c1.val<c2.val){
                if(newhead==null){
                    newhead=c1;
                    newlast=c1;//此时只有一个元素,头就是尾
                }else{
                    newlast.next=c1;
                    newlast=c1;
                }
                c1=c1.next;
            }else{
                if(newhead==null){
                    newhead=c2;
                    newlast=c2;//此时只有一个元素,头就是尾
                }else{
                    newlast.next=c2;
                    newlast=c2;
                }
                c2=c2.next;
            }
        }
        if(c1==null){
            newlast.next=c2;
        }
        if(c2==null){
            newlast.next=c1;
        }
        return newhead;

假头法改进

    public ListNode mergeTwoLists2(ListNode list1, ListNode list2){
        ListNode newhead = new ListNode(937854328);
        ListNode c1 = list1;
        ListNode c2 = list2;
        ListNode newlast = newhead;
        while (c1 != null && c2 != null) {
            if (c1.val < c2.val) {
                newlast.next=c1;
                c1 = c1.next;
            } else {
                newlast.next=c2;
                c2 = c2.next;
            }
            newlast=newlast.next;
        }
        if (c1 == null) {
            newlast.next = c2;
        } else {
            newlast.next = c1;
        }
        return newhead.next;
    }

四、指定数值分割链表

题目链接

https://www.nowcoder.com/practice/0e27e0b064de4eacac178676ef9c9d70?

方法:遍历整个链表,如果当前的值小于给定值则存入链表1,否则存入链表2,然后将两个链表连接在一起就行了。代码如下:

    public ListNode partition(ListNode pHead, int x) {
        ListNode fakehead1=new ListNode(937854328);
        ListNode last1= fakehead1;
        ListNode fakehead2=new ListNode(1357820039);
        ListNode last2=fakehead2;
        ListNode c=pHead;
        while(c!=null){
            if(c.val<x){
                last1.next=c;
                last1=c;
            }else{
                last2.next=c;
                last2=c;
            }
            c=c.next;
        }
        last1.next=null;
        last2.next=null;
        last1.next=fakehead2.next;
        return fakehead1.next;
    }

五、删除有序链表的重复节点

题目链接

https://www.nowcoder.com/practice/fc533c45b73a41b0b44ccba763f866ef?

方法一:遍历法

        首先我们知道这是一个有序的链表,也就是说,这个链表的重复节点都是连在一块的。我们可以用三个指针来遍历整个链表,如果该节点的值不等于下个节点的值,我们就直接向下走,如果相等的话,我们就将next指针逐个往后移,一直移到不等于该节点的节点处,并将prev指针的指向改为该节点。同时需要考虑特殊情况,这样我们就实现了重复节点的删除。代码如下:

    public ListNode deleteDuplication(ListNode pHead) {
        if(pHead==null){
            return null;
        }
        ListNode fakehead=new ListNode(937854328);
        fakehead.next=pHead;
        ListNode p=fakehead;
        ListNode c=pHead;
        ListNode n=c.next;
        while(c.next!=null){
            if(c.val!= n.val){
                p=c;
                c=n;
                n=n.next;
            }else{
                while(c.val==n.val){
                    n=n.next;
                    if(n==null){
                        p.next=null;
                        return fakehead.next;
                    }
                }
                p.next=n;
                c=n;
                n=n.next;
            }
        }
        return fakehead.next;
    }

方法二:递归法

        递归法的代码较为简洁,但是递归法难度更高。我们需要知道,函数的功能需要删除重复的节点,于是当节点为null或者节点个数为1个的时候哦我们可以直接返回。当当前节点与下一个节点的值不同时,我们可以保留该节点,并且将该节点指向去重后的以下个节点为头的链表。当当前的节点的值与下个节点的值相同的时候,我们需要一直跳到下个不相等的节点或者为null的节点,我们返回去重后的以不相等的节点为头的链表。实际还是在节点为空或者一个的时候返回。

public class Solution {
    public ListNode deleteDuplication(ListNode pHead) {
        // 递归出口:当「输入节点为空」或者「不存在下一节点」,直接返回
        if (pHead == null || pHead.next == null) return pHead;

        if (pHead.val != pHead.next.val) {
            // 若「当前节点」与「下一节点」值不同,则当前节点可以被保留
            pHead.next = deleteDuplication(pHead.next);
            return pHead;
        } else {
            // 若「当前节点」与「下一节点」相同,需要跳过「值相同的连续一段」
            ListNode tmp = pHead;
            while (tmp != null && tmp.val == pHead.val) tmp = tmp.next;
            return deleteDuplication(tmp);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值