单链表 | 常见习题总结

以下习题中的单链表以不带头节点的单链表为例。

1.输出倒数第k个节点的值

思路: 统计链表节点的个数length,让p从头节点遍历走length-k步,即得到倒数第k个节点的位置

  • 代码如下:
public int getLength(){
   int count=0;
   for(Entry p=headEntry;p!=null;p=p.getNext()){
        count++;
   }
   return count;
}
public static <E>E findEntry(Link<E> link, int k) {
   if(k>link.getLength()|| k<0){
       return null;
   }
   int length=link.getLength();
   int len=length-k;
   Entry<E> p=link.headEntry;
   for(;--len>=0;p=p.getNext()){
       ;
   }
   return p.getValue();
}
2.单链表逆置

思路: 定义三个变量p、q、s,其中p、q用来逆置,s用来标记节点;
①将q的next域指向p的地址
在这里插入图片描述
②p走到q的位置③q走到原来s的位置④s后移
在这里插入图片描述
继续以上步骤,直到q != null或p.next != null 结束循环,最后更新头尾节点
在这里插入图片描述

  • 代码如下:
public static <E extends Comparable<E>>void reverse(Link<E> link){
   if(link.getLength()==1){
         return;
   }
   Entry<E> p=link.headEntry,q=p.getNext(),s=q.getNext();
   while(q!=null){
       q.setNext(p);
       p=q;
       q=s;
       if (s != null) {
            s=s.getNext();
       }
   }
   link.tailEntry=link.headEntry;//原来的头部是新尾部
   link.headEntry=p;//p为新头部
   link.tailEntry.setNext(null);//将新尾部节点next域置为空
}

测试1,2题:

public static void main(String[] args){
    Link<Integer> link1 = new Link<>();
    Link<Integer> link2 = new Link<>();
    //构建link1
    link1.addHead(3);//3
    link1.addHead(9);//9 3
    link1.addTail(5);//9 3 5
    link1.addHead(16);//16 9 3 5
    //构建link2
    link2.addHead(2);//2
    link2.addHead(5);//5 2
    link2.addTail(4);//5 2 4
    link2.addTail(1);//5 2 4 1
    link2.addTail(7);//5 2 4 1 7
    link2.addHead(9);//9 5 2 4 1 7
    link2.addTail(8);//9 5 2 4 1 7 8
    System.out.println("链表1:");
    link1.show();
    System.out.println();
    System.out.println("链表2:");
    link2.show();
    System.out.println();
    //第1题
    System.out.println("倒数第k个节点为:"+link2.findEntry(link2,4));
    //第2题
    System.out.println("逆置链表后");
    Link.reverse(link1);//16 9 3 5 --> 5 3 9 16
    link1.show();
}

运行结果:
在这里插入图片描述

3.若两链表相交,输出相交节点

思路: ①分别统计两个链表的节点个数length1,length2
②通过比较length,得到长短链表LongLink、ShortLink,让长链表先走差值步
③此时让LongLink、ShortLink同时出发,当两个节点的地址相同时,找到相交节点

注意:此时需要设计一下两个链表让其相交:
如:链表一:5 2 4 1 7,链表二:3 5,假设我们想要实现如图所示链表的相交,首先需要找到链表二中5所在节点位置,其次将其next域指向链表一中1所在节点的地址
在这里插入图片描述

  • 代码如下:
//找节点
public Entry getEntry(E value){
   for(Entry<E> p=headEntry;p!=null;p=p.getNext()){
       if(p.getValue().compareTo(value)==0){
            return p;
       }
    }
    return null;
}
public static <E>E getMeetEntry(Link<E> link1, Link<E> link2){
   //1.统计链表长度
   int length1=link1.getLength();
   int length2=link2.getLength();
   int difference=Math.abs(length1-length2);  //获得差值
   //2.长链表先走差值步
   Entry<E> longLink=length1>length2?link1.headEntry:link2.headEntry;
   Entry<E> shortLink=length1<=length2?link1.headEntry:link2.headEntry;
   for(;--difference>=0;longLink=longLink.getNext()){
       ;
   }
   //3.长短链表同时向后走
   for(;longLink!=null;longLink=longLink.getNext(),shortLink=shortLink.getNext()){
        if(longLink==shortLink){
             return longLink.getValue();
        }
   }
   return null;
}
  • 测试第3题:
public static void main(String[] args) {
    Link<Integer> link1 = new Link<>();
    Link<Integer> link2 = new Link<>();
    //构建link1
    link1.addHead(3);
    link1.addHead(9);
    link1.addTail(5);
    link1.addHead(16);//16 9 3 5
    //构建link2
    link2.addHead(2);
    link2.addHead(5);
    link2.addTail(4);
    link2.addTail(1);
    link2.addTail(7);
    link2.addHead(9);
    link2.addTail(8);//9 5 2 4 1 7 8
    System.out.println("链表1:");
    link2.show();
    System.out.println();
    System.out.println("链表2:");
    link1.show();
    System.out.println();
    //获取两链表的相交节点
    Entry<Integer> meetEntry = link2.getEntry(4);
    if(meetEntry==null)
        return;
    //获取相交节点前一个节点
    Entry<Integer> linkList=link1.getEntry(5);
    //构建相交链表
    linkList.setNext(meetEntry);
    System.out.println("两链表相交入口点为:"+ Link.getMeetEntry(link2,link1));
    System.out.println("构建得到的新链表:");
    link1.show();
}

运行结果:
在这里插入图片描述

4.判断单链表是否有环

思路: 快慢引用
定义fast、slow两个变量,slow走一步,fast走两步,若有环,则fast与slow必相遇

  • 代码如下:
public Entry<E> isCircle(){
   Entry<E> fast=headEntry;
   Entry<E> slow=headEntry;
   do{         //为使循环能顺利进入采用do...while循环语句
       if(fast==null || fast.getNext()==null){
            return null;
       }
       slow=slow.getNext();
       fast=fast.getNext().getNext();
    }while(slow!=fast);
    return fast;
}
5.若有环,输出环的入口节点

思路: 若有环,则必有相遇点
若将该环链从入口点切开,起始点到入口点的距离为x,入口点到相遇点的距离为y,相遇点到入口点的距离为z;在这里插入图片描述
则根据 “快慢引用原理” 可知:fast走过的距离=2×slow走过的距离,则x+y+z+y=2(x+y),推导得:x=z;即从起始点到入口点的距离=从相遇点到入口点的距离。故在起始点定义变量p指向头节点,在相遇点定义q,同时出发,直到p、q相遇;

  • 代码如下:
public E getCircleMeetEntry(){
   Entry<E> meet=isCircle();     //获得相遇节点
   if(meet==null){
       return null;
   }
   Entry<E> p=headEntry;
   Entry<E> q=meet;
   while(p!=q){
       p=p.getNext();
       q=q.getNext();
   }
   return p.getValue();
}

测试4,5题:

public static void main(String[] args) {
    Link<Integer> link2=new Link<Integer>();
    link2.addHead(2);//2
    link2.addHead(5);//5 2
    link2.addTail(4);//5 2 4
    link2.addTail(1);//5 2 4 1
    link2.addTail(7);//5 2 4 1 7
    //构建环
    Entry<Integer> m=link2.getEntry(7);
    Entry<Integer> n=link2.getEntry(2);
    m.setNext(n);
    System.out.println("入口点为:"+link2.getCircleMeetEntry());
}

运行结果:
在这里插入图片描述

6.两个有序链表合并为一个有序链表

思路: ①比较两个链表头节点p1,p2的大小,用该节点标记新链表尾部(假设找到的较小节点为p1)
②p1后移,接着比较p1与p2的大小,将值小的链接到p1尾部
③循环上两步,直到p1或p2走为空,停止循环
④更新尾部节点

  • 代码如下:
public Entry<E> mergeLink(Link<E> link){
   if(link == null || link.headEntry==null)
       return this.headEntry;
   Entry<E> p1 = this.headEntry;//p1遍历当前链表
   Entry<E> p2 = link.headEntry;//p2遍历link链表
   //新链表头:
   Entry<E> newHeadEntry = p1.getValue().compareTo(p2.getValue())>0 ? p2 : p1;
   Entry<E> tailNewEntry = newHeadEntry;//标记新链表尾部
   if(newHeadEntry == p1){
       p1 = p1.getNext();
   }else{
       p2 = p2.getNext();
   }
   //比较p1 和 p2节点所对的值,值小的连接到新链表尾部
   while(p1!=null && p2!=null){
       if(p1.getValue().compareTo(p2.getValue()) < 0){
           tailNewEntry.setNext(p1);
           tailNewEntry = p1;//p1节点作为新链表尾部
           p1 = p1.getNext();
       }else{
           tailNewEntry.setNext(p2);
           tailNewEntry = p2;//p2节点作为新链表尾部
           p2 = p2.getNext();
       }
    }
    //p1走完
    if(p1 == null) {
         tailNewEntry.setNext(p2);
    }
    //p2走完
    if(p2 == null) {
         tailNewEntry.setNext(p1);
    }
    return newHeadEntry;
}

测试:

public static void main(String[] args) {
    Link<Integer> link1=new Link();
    Link<Integer> link2=new Link();
    link1.addTail(1);
    link1.addTail(3);
    link1.addTail(8);
    link1.addTail(12);
    link1.addTail(13);
    link1.addTail(16);
    link2.addTail(2);
    link2.addTail(5);
    link2.addTail(6);
    link2.addTail(14);
    Entry<Integer> f=link1.mergeLink(link2);
    for(;f!=null;f=f.getNext()){
        System.out.print(f.getValue()+" ");
    }
}

运行结果:
在这里插入图片描述

7.复杂链表的拷贝问题

首先明确什么是复杂链表:

复杂链表区别于普通单链表和双向链表。
所谓复杂链表,即每个节点中包含节点值以及两个指针:
其中next指针指向下一个节点,另一个特殊指针random随机指向任意一个节点

形如:(其中random指针我随意指的)
在这里插入图片描述
思路:
1.复制原链表,在原链表的每个节点后复制该节点。比如:原链表为:A-B-C,复制后为:A-A’-B-B’-C-C’
2.设置random指针的指向。复制节点的random指针指向原节点random指针的下一个节点
在这里插入图片描述
由上图可知,B’.random=B.random.next

3.拆分链表
在这里插入图片描述

  • 代码如下:
public class CopyComplexListTest<T> {
    static class Node<E>{
        E value;
        Node next;
        Node random;

        public Node() {
        }

        public Node(E value) {
            this.value = value;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("value = " + value);
            sb.append(", next = " + (next == null ? "null" : next.value));
            sb.append(", random = " + (random == null ? "null" : random.value));
            return sb.toString();
        }
    }

    //复制原链表
    public void copyList(Node<T> head){
        Node<T> node = head;
        while(node != null){
            Node copyNode = new Node();
            copyNode.value = node.value;
            copyNode.next = node.next;
            copyNode.random = null;

            node.next = copyNode;
            node = copyNode.next;
        }
    }

    //设置random指针
    public void setRandom(Node<T> head){
        Node<T> node = head;
        while(node != null){
            Node<T> copyNode = node.next;
            if(node.random != null){
                copyNode.random = node.random.next;
            }
            node = copyNode.next;
        }
    }

    //拆分链表
    public Node<T> disconnectList(Node<T> head){
        Node<T> node = head;
        Node<T> copyHead = null;
        Node<T> copyNode = null;

        if(node != null){
            copyHead = node.next;
            copyNode = node.next;
            node.next = copyNode.next;
            node = node.next;
        }

        while(node != null){
            copyNode.next = node.next;
            copyNode = copyNode.next;

            node.next = copyNode.next;
            node = node.next;
        }
        return copyHead;
    }

    public Node<T> copy(Node<T> head){
        copyList(head);
        setRandom(head);
        return disconnectList(head);
    }

    public static void main(String[] args) {
        CopyComplexListTest<Integer> test = new CopyComplexListTest<>();

        //构建复杂链表
        Node<Integer> head = new Node<>(1);
        Node<Integer> node2 = new Node<>(2);
        Node<Integer> node3 = new Node<>(3);
        Node<Integer> node4 = new Node<>(4);
        Node<Integer> node5 = new Node<>(5);

        head.next = node2;
        head.random = node3;

        node2.next = node3;
        node2.random = node5;

        node3.next = node4;

        node4.next = node5;
        node4.random = node2;

        Node<Integer> copyHead = test.copy(head);

        Node<Integer> node = copyHead;
        for(;node!=null;node=node.next){
            System.out.println(node);
        }
    }
}

运行结果:
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值