单链表相关题目

本篇包括:
  • [最快时间内找到单链表的倒数第K个节点]
  • [最快时间内删除单链表的倒数第K个节点]
  • [判断单链表是否有环,环的入口,环的长度]
  • [判断两个单链表是否相交,交点?]
  • [单链表的逆置(头插法)]
  • [单链表的反转]
  • [ O(1)时间内删除单链表的节点]
  • [合并两个有序的单链表为一个单链表]

关于单链表及结点类的定义、以及常用到的添加、删除、打印等方法,在上一篇博客中进行了简单的书写,详情点击:单链表常用方法

1. 最快时间内找到单链表的倒数第K个节点

要找到单链表的一个结点至少要遍历链表一遍,时间复杂度O(n)

这里假设K = 3;要找到单链表的倒数第三个结点,首先定义cursor1 和cursor2,让cursor1先遍历数组,走k-1步,然后cursor1 和cursor2一样的步进继续遍历链表,当cursor1遍历到链表的尾结点时,cursor1 刚好到链表的倒数第K个结点,如图:

在这里插入图片描述
编码:

    //最短时间内找到单链表的倒数第K个结点
    public T fidLastK(int k) {
        if (!checkRange(k)) {
            throw new UnsupportedOperationException("k不合法");
        }
        Entry<T> cursor1 = this.head;
        Entry<T> cursor2 = this.head;
        while (k-1 > 0) {
            cursor1 = cursor1.next;
            k--;
        }
        while (cursor1.next != null) {
            cursor1 = cursor1.next;
            cursor2 = cursor2.next;
        }
        return cursor2.data;
    }
2. 最快时间内删除单链表的倒数第K个节点

要删除倒数第k个结点,就需要找到该节点的前驱,即倒数第k+1个结点,类似于上面寻找倒数第k个结点的方法,这次需要找的是倒数第k+1个结点;

相同的道理,定义cursor1 和cursor2,让cursor1先遍历数组,这次需要走k步,然后cursor1 和cursor2一样的步进继续遍历链表,当cursor1遍历到链表的尾结点时,cursor1 刚好到链表的倒数第k+1个结点,如图:

让cursor2.next = cursor.next.next就可以删除倒数第k个结点了;
在这里插入图片描述
编码:

    public void removeLastK(int k) {
        if (!checkRange(k)) {
            throw new UnsupportedOperationException("k不合法");
        }
        Entry<T> cursor1 = this.head;
        Entry<T> cursor2 = this.head;
        while (k > 0) {
            cursor1 = cursor1.next;
            k--;
        }
        while (cursor1.next != null) {
            cursor1 = cursor1.next;
            cursor2 = cursor2.next;
        }
        cursor2.next = cursor2.next.next;
    }
3. 判断单链表是否有环
	//制造一个环
    public void creatLoop() {   
        Entry cur = this.head;
        while (cur.next != null) {
            cur = cur.next; // 找到单链表的尾结点;
        }
        cur.next = this.head.next.next.next;
        //这里可以随意.next,只要不要超过尾结点就ok;
    }

如图,尾结点next指向了头结点后的第三个数据节点;
在这里插入图片描述

	//判断单链表是否有环
    public boolean isLoop() {
        Entry<T> fast = this.head;
        Entry<T> slow = this.head;
        while (fast != null && fast.next != null) { 
            fast = fast.next.next; //fast一次遍历两个结点
            slow = slow.next;
            if (fast == slow) { //若fast和slow相遇,说明有环
                return true;
            }
        }
        return false;
    }
环的入口

在上面提到的判断单链表是否有环时,当fast和slow相遇则说明有环,此时相遇的交点即是环的入口

    public int EnterEntry() {
        Entry fast = this.head;
        Entry slow = this.head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (slow == fast) {
                break;
            }
        }
        return fast.data;
    }
环的长度
    //环的长度
    public int loopLength() {
        Entry fast = this.head;
        Entry slow = this.head;
        boolean flg = false;
        int len = 0;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (slow == fast && flg == false) {
                flg = true;
            }
            if (slow == fast && flg == true) {
                break;
            }
            
            if (flg == true) {
                len++;
            }
        }
        return len;
    }
4. 判断两个单链表是否相交

首先制造两个相交的单链表:
为了画图方便这里用一样的链表复制了下,看指向就行不用纠结是一个链表啦
在这里插入图片描述

    //制造两个相交的单链表
    public static void creatCut(TestLink link1,TestLink link2) {
        TestLink.Entry head1 = link1.getHead();
        TestLink.Entry head2 = link2.getHead();
        head1.next.next = head2.next.next.next.next;
    }

此时link1的长度为4,link2的长度为5,head1指向link2,head2指向link1,myLen等于1,先让head1遍历myLen个结点,然后两个指针一起遍历链表时最终会相遇;
在这里插入图片描述

    public static boolean isCut(TestLink link1,TestLink link2) {
        TestLink.Entry head1 = link1.getHead(); //用head1指向长的单链表
        TestLink.Entry head2 = link2.getHead(); //用head2指向短的单链表
        int len1 = link1.getLength(); //link1的链表长度
        int len2 = link2.getLength(); //link2的链表长度
        int myLen = len1 - len2; //两个链表长度之差
        if (myLen < 0) { //若myLen < 0则说明head1指向的是短链表,此时交换head1与head2的指向
            head1 = link2.getHead();
            head2 = link1.getHead();
            myLen = len2 - len1;
        }
        int count = 0;
        while (count < myLen) { //让指向长链表的head1先遍历myLen个结点
            head1 = head1.next;
            count++;
        }
        
        //此时link1剩余的链表长度与link2的长度相等
        //同时开始遍历链表,若相交则head1与head2一定会相遇
        while (head1.next != null && head2.next != null) {
            head1 = head1.next;
            head2 = head2.next;
            if (head1 == head2) {
                return true;
            }
        }
        return false;
    }

测试及结果:

    public static void main(String[] args) {
        TestLink link1 = new TestLink();
        TestLink link2 = new TestLink();
        for (int i = 0;i < 5;i++) {
            link1.insertHead(i);
        }
        for (int i = 2;i < 7;i++) {
            link2.insertTail(i);
        }
        creatCut(link1,link2);
        System.out.println(isCut(link1,link2));
    }

在这里插入图片描述

交点?

在上面的描述中可以看出,Link1 == link2时说明两个链表相交,此时link1/link2就是两个链表的交点;

5. 单链表的逆置(头插法)

定义cursor为第一个数据节点,将头结点和数据节点断开,curNext是cursor的后继节点,然后用头插法将cursor插入head结点的后面,再将curNext赋给cursor以此类推,直到将之前链表的尾结点插入新的链表中,即完成了链表的逆置;
在这里插入图片描述
编码:

public void reverseLink() {
        Entry cur = this.head.next;
        Entry curNext = null;
        this.head.next = null;
        while (cur != null) {
            curNext = cur.next;
            cur.next = this.head.next;
            this.head.next = cur;
            cur = curNext;
        }
    }
6. 单链表的反转
    //单链表的反转
    public Entry reverseLink1() {
        Entry cur = this.head;
        Entry curNext = null;
        Entry pre = null;
        Entry reverHead = null;    //反转后的头节点
        while (cur != null) {
            curNext = cur.next;
            if (curNext == null) {
                reverHead = cur;
            }
            cur.next = pre;
            pre = cur;
            cur = curNext;
        }
        return reverHead;
    }
7. O(1)时间内删除单链表的节点
    //O(1)时间内删除单链表的节点
    public void deleteEntry(Entry entryStart,Entry entryDelete) {
        Entry entryDeleteNext;

        if (entryDelete.next != null) { //如果删除的节点不是尾节点
            entryDeleteNext = entryDelete.next;
            entryDelete.data = entryDeleteNext.data;
            entryDelete.next = entryDeleteNext.next;
            entryDeleteNext.next = null;
        } else {
            while (entryStart.next != entryDelete) {
                if (entryStart.next.next != null) {
                    entryStart = entryStart.next;
                }
                entryStart.next = null;
                entryDelete = null;
            }
        }
    }
8. 合并两个有序的单链表为一个单链表

默认两个链表的数据都是从小到大排列;
emmmmm不晓得注释这样写小伙伴能更好理解嘛

	//合并两个有序单链表
    public static TestLink.Entry mergeLink(TestLink link1, TestLink link2) {
        TestLink.Entry newHead = null; // 定义一个空的头结点
        TestLink.Entry p1 = link1.getHead(); //p1指向link1的头结点
        TestLink.Entry p2 = link2.getHead();//p2指向link2的头结点

        //判断两个链表第一个数据节点值的大小
        //合并后的链表的头结点 指向 第一个数据节点值 较小的链表的头结点
        if (p1.next.data < p2.next.data) {
            newHead = link1.getHead();
        } else {
            newHead = link2.getHead();
        }
        //让P1,p2指向各自链表的第一个数据节点
        p1 = p1.next;
        p2 = p2.next;
        
        //将头结点newHead作为该函数的返回值返回,此处重新给一个等于newHead的头结点
        TestLink.Entry tmpHead = newHead;
        while (p1 != null && p2 != null) { //在两个链表都没有遍历完时
            //让temHead指向小的结点,p继续向后遍历原来的结点
            //然后tmpHead指向新链表的下一个结点,此时tmpHead为新链表的尾结点
            if (p1.data < p2.data) { 
                tmpHead.next = p1;
                p1 = p1.next;
            } else {
                tmpHead.next = p2;
                p2 = p2.next;
            }
            tmpHead = tmpHead.next;
        } 
        //退出循环则说明有一个链表已经遍历完成,此时将另一个没遍历完的链表剩余部分跟新链表的尾结点连接起来
        if (p1 == null) {
            tmpHead.next = p2;
        }
        if (p2 == null) {
            tmpHead.next = p1;
        }
        
        return newHead;  //返回合并后新链表的头结点
    }

调用上面的函数会返回一个TestLink.Entry类型的头结点,此时需要重新写一个打印的函数;

 	public static void showMerge(TestLink.Entry entry) {
        TestLink.Entry cur = entry.next;
        while (cur != null) {
            System.out.print(cur.data + " ");
            cur = cur.next;
        }
        System.out.println();
    }

测试及结果:

    public static void main(String[] args) {
        TestLink link1 = new TestLink();
        TestLink link2 = new TestLink();
        for (int i = 0;i < 5;i++) {
            link1.insertTail(i);
        }
        for (int i = 2;i < 7;i++) {
            link2.insertTail(i);
        }
        link1.show();
        link2.show();
        showMerge(mergeLink(link1,link2));
    }

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值