单链表(Ⅱ)

本文详细介绍了链表操作的各种方法,包括使用虚拟头节点进行插入、删除、输出等操作,以及解决LeetCode相关链表问题的算法,如删除重复元素、删除中间节点、反转链表、找链表中点、判断链表环、合并两个有序链表等。通过这些实例,深入理解链表数据结构及其在实际问题中的应用。
摘要由CSDN通过智能技术生成

单链表Ⅱ


一、虚拟头节点

在链表内部创建一个不存在存储具体元素,只作为链表头
Node dummyHead = new Node(-1);
所有链表的插入和删除->在中间位置进行

1.增加

package seqlist;
//带头单链表
public class SingleLinkedListWithHead {
    //当前存储的元素个数
    private int size;
    //虚拟头节点
    private Node dummyHead= new Node(-1);
    //添加方法
    public  void addIndex(int index,int val){
     //判断index的合法性
        if (index<0||index >size) {
            System.err.println("add index illagal!");
            return ;
        }
        //插入全部是中间节点
        Node node = new Node(val);
        //找到待插入位置的前驱
        Node prev= dummyHead;
        for (int i = 0; i < index; i++) {
            prev=prev.next;
        }
        //prev指向待插入位置的前驱
        node.next=prev.next;
        prev.next=node;
        size++;
    }
    public void addFirst(int val){
        addIndex(0,val);
    }
    public void addLast(int val){
        addIndex(size,val);
    }
    public String toString(){
        String ret="";
        Node node= dummyHead.next;
        while(node!=null){
            ret+=node.val;
            ret+="->";
            node=node.next;
        }
        ret+="NULL";
        return ret;
    }
}

测试代码

package seqlist;
public class Test {
   public static void main(String[] args) {
   SingleLinkedListWithHead singleLinkedListWithHead=new SingleLinkedListWithHead();
        singleLinkedListWithHead .addLast(1);
        singleLinkedListWithHead.addLast(2);
        singleLinkedListWithHead.addLast(3);
        singleLinkedListWithHead.addFirst(4);
        singleLinkedListWithHead.addIndex(1,10);
        System.out.println(singleLinkedListWithHead);
        //4,10,1,2,3
    }

2.输出

  public String toString(){
        String ret="";
        Node node= dummyHead.next;
        while(node!=null){
            ret+=node.val;
            ret+="->";
            node=node.next;
        }
        ret+="NULL";
        return ret;
    }

3.删除

 public void removeIndex(int index){
        if (index<0||index>=size) {
            System.err.println("remove index illegal!");
            return;
        }
        //删除中间节点
        Node prev=dummyHead;
        //找到待删除位置的前驱
        for (int i = 0; i < index; i++) {
            prev=prev.next;
        }
        //prev指向待删除位置的前驱
        prev.next=prev.next.next;
        size--;
    }

测试代码

     //4,10,1,2,3
        singleLinkedListWithHead.removeIndex(4);
        System.out.println(singleLinkedListWithHead);

4.虚拟头节点LeetCode203

//虚拟头节点
    public ListNode removeElements(ListNode head,int val){
        ListNode dummyHead = new ListNode(-1);
        //链接原来的链表
        dummyHead.next=head;
        ListNode prev = dummyHead;
        while (prev.next!=null) {
            if (prev.next.val ==val ) {
                prev.next=prev.next.next;
            }else {
                //prev下一个不是待删除节点,prev继续向后移动位置
                prev=prev.next;
            }
        }
        return dummyHead.next; 
    }

二、单链表习题

1.LeetCode83

给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。

1.判断重复元素->需要两个引用来对比内容,prev.val=cur.val
prev永远指向第一个重复节点,删除对应cur节点,保留prev
2.终止条件
while()->当前用的元素是谁,就要保证谁不为空
所以边界条件为:while(cur!=null)
3.只有当prev和cur值不相等时,才能移动prev引用(保证删除后若再次出现重复元素不会漏删)
4.插入和删除index位置的节点,找到index的前驱从dummyHead开始向后走。走index步刚好走到前驱(比之前的单链表多走了一个虚拟头节点)
prev=dummyHead;
for(int i=0;i<index;i++) { prev=prev.next;}

2.LeetCode82

给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。

1.判断重复
prev与cur,发现找到重复元素时,prev指向第一个重复元素,此时prev指向的元素就删除不掉->不能使用prev和cur来判断重复,需要再次引用一个新的引用next,cur.val==next.val判断重复节点
在这里插入图片描述

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
    ListNode dummyHead=new ListNode(101);
        dummyHead.next=head;
        ListNode prev=dummyHead;
        ListNode cur=prev.next;
        while(cur!=null){
            ListNode next=cur.next;
            if(next==null){ 
                return dummyHead.next;
            }else{
                //至少有两个节点
                if(cur.val!=next.val){
                    //三引用同时向后移动
                    prev=prev.next;
                    cur=cur.next;
                }else {
                    //将用到元素都要进行判断是否为空
                    //保证所有的引用都不为空,否则出现空指针问题
                    while(next!=null&&cur.val==next.val){
                       next=next.next;
                    }
                    //prev指向重复节点的前驱
                    //next指向重复节点的后继
                    prev.next=next;
                    //更新cur的指向
                    cur=next;
                }
            }
        }
        return dummyHead.next;
    }
}

3.LeetCode206

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
方法一:头插法

public class Num206 {
    //1.头插法
    public ListNode reverseList(ListNode head) {
        if (head == null||head.next==null) {
            return head;
        }
        //新链表的虚拟头节点
        ListNode dummyHead =new ListNode(5001);
        //边遍历原链表,边头插新链表
        while(head!=null){
            //原链表1,2,3,4,5,
            //不断创建新节点
            //dummyHead->1,1后面是空
            //dummyHead->2->1 以此类推
            ListNode node =new ListNode(head.val);
            node.next=dummyHead.next;
            dummyHead.next=node;
            head=head.next;
        }
        return dummyHead.next;
    }

}

方法二:原地移动-O(1)
原链表的next不再指向后继而指向前驱

   if (head == null||head.next==null) {
            return head;
        }
        ListNode prev=null;
        //当前需要处理的节点(需要反转)
        ListNode cur =head;
        while(cur!=null){
            //暂存一下下一个需要处理的节点
            ListNode next=cur.next;
            cur.next=prev;
            prev=cur;
            cur=next;
        }
        return prev;

方法三:递归法
在这里插入图片描述

   if (head == null||head.next==null) {
            return head;
        }
        ListNode sec=head.next;
        //反转第二个节点之后的子链表
        ListNode newHead=reverseList(head.next);
        //3.将sec.next=head;
        //head.next=null;
        sec.next=head;
        head.next=null;
        return newHead;

4.LeetCode876

给定一个头结点为 head 的非空单链表,返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

public class Num876 {
    public ListNode middleNode(ListNode head) {
        ListNode fast=head;
        ListNode low=head;
        while(fast !=null && fast.next!=null){
            low=low.next;
            fast=fast.next.next;
        }
        return low;
    }
}

5.剑指 Offer 22. 链表中倒数第k个节点

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。

例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。
在这里插入图片描述

//未验证k的合法性
class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode fast=head ,low = head;
        //fast 先走K步,保持fase,low的相对距离
        for(int i=0;i<k;i++){
            fast=fast.next;
        }
        while(fast!=null){
            fast=fast.next;
            low=low.next;
        }
        return low;
    }
}

6.LeetCode234:回文链表

即:找到中间节点+反转中间节点之后的子链表

class Solution {
 //回文链表
    public boolean isPalindrome(ListNode head) {
        //1.找到中间节点
        ListNode middleNode=middleNode(head);
        //2.反转中间节点后的链表,一定比原链表的一半短
        ListNode l2=reverseList(middleNode);
        //同时遍历原链表l1
        while (l2!=null){
            if (l2.val !=head.val ) {
                return false;
            }
            l2=l2.next;
            head=head.next;
        }
        return true;
    }
    //反转
    public ListNode reverseList(ListNode head) {
        if (head == null||head.next==null) {
            return head;
        }
        ListNode sec=head.next;
        //反转第二个节点之后的子链表
        ListNode newHead=reverseList(head.next);
        //3.将sec.next=head;
        //head.next=null;
        sec.next=head;
        head.next=null;
        return newHead;
    }
    //中间节点
    public ListNode middleNode(ListNode head) {
        ListNode fast=head;
        ListNode low=head;
        while(fast !=null && fast.next!=null){
            low=low.next;
            fast=fast.next.next;
        }
        return low;
    }
}

7.LeetCode141:链表带环Ⅰ

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false 。

//判断链表是否带环
public class Num141 {
    public boolean hasCycle(ListNode head) {
        ListNode fast = head, low = head;
        while(fast!=null&&fast.next!=null){
            fast=fast.next.next;
            low=low.next;
            //指向同一节点
            if (fast == low) {
                return true;
            }
        }
        return false;
    }
}

8.LeetCode21:合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
    //判断边界条件,l1\l2\l1和l1为空
        if (list1==null){
            return list2;
        }
        if (list2==null){
            return list1;
        }
        ListNode dummyHead=new ListNode(-1);
        ListNode last=dummyHead;
        while (list1!=null&&list2!=null){
            if (list1.val<=list2.val){
                last.next=list1;
                last=list1;
                list1=list1.next;
            }else{
                last.next=list2;
                last=list2;
                list2=list2.next;
            }
        }
        //剩余节点
        if (list1==null){
            last.next=list2 ;
        }
        if (list2 == null) {
            last.next=list1;
        }
        return dummyHead.next;
    }
}

9.LeetCode:分割链表

给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。

你不需要 保留 每个分区中各节点的初始相对位置。

public class Num0204 {
    public ListNode partition(ListNode head, int x) {
        ListNode bigHead=new ListNode(-1);
        ListNode smallHead=new ListNode(-1);
        //按照升序插入,需要尾插
        //分别指向两个子链表的尾部
        ListNode smalltTail=smallHead;
        ListNode bigTail=bigHead;
        //边遍历原链表,根据剩下节点依次放在子链表中
        while (head!=null){
            if (head.val<x){
                smalltTail.next=head;
                smalltTail=head;
            }else{
                bigTail.next=head;
                bigTail=head;
            }
            head=head.next;
        }
        //将bigTail尾部元素置为null
        bigTail.next=null;
        //拼接两个链表
        smalltTail.next=bigHead.next;
        return smallHead.next;

    }
}

10.LeetCode160:相交链表

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
在这里插入图片描述
设A:未相交链表为A,B:未相交链表为B,相交链表为C。按照下面思路有,A+C+B=B+C+A。
让A和B遍历自己的链表当走到NULL时,遍历对象链表。如果有交点在交点处汇合,如果没交点在结尾NULL处汇合。

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode pA=headA,pB=headB;
        while (pA!=pB){
            pA=pA==null?headB:pA.next;
            pB=pB==null?headA:pB.next;
        }
        return pA;
    }
}

11.LeetCode141:链表带环Ⅱ

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

public class Solution {
    public ListNode detectCycle(ListNode head) {
         ListNode low=head,fast=head;
        while(fast!=null&&fast.next!=null){
            fast=fast.next.next;
            low=low.next;
            //此时low和fast相遇
            if (fast==low){
                //创建一个新的node,从头开始和low一起走
                ListNode third=head;
                while (low!=third){
                    low=low.next;
                    third=third.next;
                }
                return third;
            }
        }
        return null; 
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值