java数据结构-链表经典习题

前言

上一篇讲解了链表的基本操作详解,接下来练习一下链表的应用。

目录

1.删除链表中等于给定值 val 的所有节点。

 题解思路

 2.反转一个单链表

思路分析

画图分析 

 代码实现

3.链表的中间结点

思路分析

画图分析

 代码实现

 4.链表中倒数最后k个结点

第一解决办法

第二种解决办法

5.合并两个有序链表

思路分析

画图分析

 代码实现

6. 链表分割

思路分析

画图分析

 代码实现

 7.链表回文

思路分析

画图分析

代码实现

8. 相交链表

思路分析

画图分析

代码实现

9.环形链表1

思路分析

画图分析

代码实现

10.环形链表2

思路分析

画图分析

代码实现

总结


1.删除链表中等于给定值 val 的所有节点。

移除链表元素习题链接: 移除链表元素

 题解思路

实现在链表中删除所有给定的key

1.首先先遍历链表

2.如果有某个节点的val值等于vag,那么让这个节点的上一个节点next存放该节点的next(相当于就是跳过了那个相等的节点,跳过了相当于就是删除了)

3.然后继续遍历找下一个相同的,找到进行同样的操作。

4.最后解决头部问题,对链表的头的val与val进行比较,如果同则让头指向下一个节点即可。

 

代码实现

public ListNode removeElements(ListNode head, int val) {
        //看看是不是空的
        if(head == null){
            return null;
        }
        
        //定义两个节点指针,指向头另一个指向头的下一个
        ListNode pevr = head;
        ListNode cur = head.next;
        
        while(cur != null){
            //2.如果有某个节点的val值等于vag,那么让这个节点的上一个节点next存放该节点的next
            // (相当于就是跳过了那个相等的节点,跳过了相当于就是删除了)
            if(cur.val == val){
                pevr.next = cur.next;
                cur = cur.next;
               

            }else{
                pevr = cur;
                cur = cur.next;
            }
        }
         //最后解决头部问题,对链表的头的val与val进行比较,如果同则让头指向下一个节点即可。
        if(head.val == val){
            head = head.next;
        }
        return head;

    }

 2.反转一个单链表

反转链表链接:反转链表

思路分析

反转一个链表是需要改变其链表结构的,

翻转链表,就是让第二个节点开始,依次进行头插

要做到这一步,需要设置两个指针,一个指向头节点的下一个节点

然后另一个指针就是保留这个节点的下一个节点,让第一个指针节点进行头插后,

再回来还能找到下一个要插入的节点地址,然后头结点变成插入好的节点,就这样遍历一下链表即可反转完成。

步骤

1.遍历链表

2.进行头插,改变头结点

3.继续下一个节点的头插,重复遍历完即可。

画图分析 

 反转前

反转后

 代码实现

 //翻转链表,就是让第二个节点开始,依次进行头插
    public  ListNode overturn(){
        if (head ==null){
            return null;
        }

        //如果是一个结点直接
        if (head.next ==null){
            return head;
        }
        //改变链表内部结构
        ListNode cur = head.next;

        head.next = null;
        while(cur != null){
            //1.循环条件咋么设置,还是cur != null,因为最后一个是null
            // 头插能实现第一个,但是后面的2接上呢,让cur返回来就行了
            ListNode curNode = cur.next;
             cur.next = head;
             head = cur;
             cur = curNode;//当做指针
        }
        return head;
    }

分析

              ListNode curNode = cur.next;
             cur.next = head;
             head = cur;
             cur = curNode;//当做指针

3.链表的中间结点

给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结 点。

习题链接:链表中间结点

思路分析

利用两个指针,一个是慢一个快,快的是一次走两步

原理就是路程相同为800米的田径场跑道,A同学和B同学同时起跑,A同学是B同学的速度的2倍,那么当A到达终点,B肯定在中点。

1.创建两个指针同时指向头结点;

2.然后遍历一遍,注意循环结束的条件是判断快指针是否到达终点;

3.走完一遍,返回慢指针即是中间结点。

画图分析

第一步

第二步

第三步

此时当fast到达末尾,那么slow必然在中间位置。

 代码实现

//快慢,速度为2倍,同样的起点,终点一样,速度是2倍
    public ListNode middleNode(ListNode head){
        ListNode fast = head;
        ListNode slow = head;
        while( fast  != null && fast.next !=null  )//fast两倍速度会提前一半到终点
        //奇数情况fast.next ==null,偶数情况fast  == null
        //这里需要注意,这两个条件不能换过来。
            {
                
                fast = fast.next.next;//2倍速度走两步
                slow = slow.next;//慢的走一步

        }
        //走完说明slow在中间
        return slow;

    }

 4.链表中倒数最后k个结点

习题链接:链表中倒数最后k个结点_牛客题霸_牛客网 (nowcoder.com)

题目描述:输入一个长度为 n 的链表,设链表中的元素的值为 ai ,返回该链表中倒数第k个节点。

如果该链表长度小于k,请返回一个长度为 0 的链表。

第一解决办法

比如要返回倒数第2个结点,那么直接用链表的 总长度 - 2,得到的数就是头结点要走的长度。

走完,返回fast即可。

代码实现


        ListNode fast = head;
        int count = 0;
        while(fast != null){
            count++;
            fast = fast.next;
        }
        //限制一下k范围
        if(head == null || k < 0 || k >count){
            return null;
        }
        fast = head; //重置为头位置
        int len = count - k; //
        while(len > 0){
            fast = fast.next;
            len--; 

        }
        return fast;

  

第二种解决办法

思路分析

输入一个链表,输出该链表中倒数第k个结点。
1.fast 先走k-1步
2.然后slow和fast同时走
3.fast到最后slow的位置就是倒数k-1
需要注意的是要对k进行判断是否合法
还需要解决当链表为空或者fast指向了空的情况

原理fast和slow永远相差k-1步

画图分析

 代码实现

        //限制k值和防止空表
        if ( k <= 0 || head == null ) {
            return null;
        }
        ListNode fast = head;
        ListNode slow = head;

        //怎么先让fast先走k-1步
        for (int i = 0 ; i < k - 1 ; i ++) {

            fast = fast.next;//如果k>size()那么fast就指向null了
            if (fast == null) { //处理k太大问题
                return null;
            }
        }
        //同时走
        //奇偶情况是如何的呢,通过画图分析是一样的
        while (fast.next != null) {

            fast = fast.next;
            slow = slow.next;

        }
        //走完了然后勒
        //走完就说明slow就是要求的位置
        System.out.println(slow.val);
        return slow;
    

5.合并两个有序链表

习题链接:21. 合并两个有序链表 - 力扣(LeetCode)

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

思路分析

合并两个链表,要改变其结构

1.创建好一个新的链表的头结点,相当于一辆火车先有车头

2.然后让两个链表的头结点分别进行比较大小,小的先在新的链表里面进行尾插,然后头结点更换到下一个,然后继续比较大小。

3.最后比较完成,拼接完成新链表,返回新链表的头结点即可。

画图分析

 代码实现

 public ListNode mergeTwoLists(ListNode head1, ListNode head2) {
        //1.定义虚拟的字节,作为新链表头结点
       ListNode newH = new  ListNode(-1);
       ListNode tmpH = newH;
        //2.遍历进行比较大小
        while(head1 != null && head2 != null){
            if(head1.val < head2.val){
                
                tmpH.next = head1; //进行尾插到新的链表中
                head1 = head1.next; //头结点改变下一个。
                
            }else{
                
                tmpH.next = head2; 
                head2 = head2.next; 
                

            }
            tmpH = tmpH.next;//指向末尾
        }
        //可能两个链表长度不一样,出了循环进行检查
        if(head1 != null){
            tmpH.next = head1;
        }
        if(head2 != null){
            tmpH.next = head2;
        }
        //3.返回新链表头结点
        
    return newH.next;
    }

6. 链表分割

习题链接:链表分割_牛客题霸_牛客网 (nowcoder.com)

题目描述:现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。

思路分析

链表的分割,要分割的话就要分两个区间,一个存放小于x的区间,另一个则是大于x的。

1.那么就有先创建四个指针分别为第一第二区间链表的头尾指针。

2.然后对链表进行一次遍历,每个结点的值依次与x比较,然后放到对应的区间。

3.遍历完,然后进行连接成一个新的链表,再返回新链表的头结点。

需要注意的是两种情况,如果第二区间是空的则无需连接

还有就是如果第一个区间为空,那么直接返回第二区间的头结点

最后就是要手动给第二区间的最后一个结点的next置为null不然会报错异常。

画图分析

第一步,第一个结点12小于35,因此放在第一个区间be、bs这时都指向第一个结点

第二步,第二个结点值是5也小于35,放第一个区间,然后尾指针be指向他。

第三步,第三个结点值大于35,放到第二区间,后续遍历也如此,这里就不一一列出。

最后结果

 代码实现

 //会改变链表的结构
    public ListNode partition(ListNode head, int x) {
        // write code here
        
        ListNode cur = head;

        ListNode bs = null;//创建好节点类型的用于保存分割后两个区间的节点位置地址
        ListNode be = null;

        ListNode as = null;
        ListNode ae = null;

        while(cur != null){//遍历
            //分割
            if(cur.val < x ){
                if(bs == null){//如果是第一个头尾都指向他
                    bs = cur;
                    be = cur;

                }else{
                    //如果是第二个以后进行尾插
                    be.next = cur;//尾插原理
                    be = be.next;
                }

            }else{
                if(as == null){
                    as = cur;
                    ae = cur;
                }else{
                    ae.next = cur;
                    ae = ae.next;
                }

            }
           cur = cur.next;
        }
        //有一种情况,如果链表是3333,x为3,那么前面区间是空的
        //此时be.next会空指针异常
        //因此要对第一区间进行判断
        if(be == null){
            return as;//如果第一区间是空那么没必要连接,返回第二区间的头即可。
        }

        //连接起来
        //用第一个区间的尾和第二个区间的头进行连接
        be.next = as;

        //还有一个情况,就是拼接起来后,第二区间的尾节点的next有可能不为空,异常
         if(as != null ){//此时需要手动进行置空
            //第二区间没有数据情况
            ae.next = null;
        }
       return bs;//返回第一区间的头

    }

 7.链表回文

习题链接:链表的回文结构_牛客题霸_牛客网 (nowcoder.com)

题目描述:

对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。

给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。

思路分析

要判断是否是回文,那么就对前后两边进行比较,如果相同两边同时走一步,如果不是同则返回false,然后继续判断,直到结束,能走到结束说明是回文结构。

那么要实现上面的效果,首先就得需要改变链表后半部分的结构。

1.首先要用快慢指针找到中间位置,然后翻转中间后面节点(使用上面的翻转思路)。

2.翻转完成,此时slow处于链表末尾,让头head和尾slow同时往中间走,每走一步进行比较val内容是否相同,然后继续下一步,直到循环结束。

3.走完返回true。

需要注意的是需要区分奇数个节点和偶数个节点的区别就行。

画图分析

1.快慢指针取中间

2.翻转后面节点

3.前后往中走

4.解决偶数问题

代码实现

    public boolean chkPalindrome(ListNode head) {     
    if(head == null || head.next == null){
            return true;
        }
        // write code here
        //中间位置
        ListNode fast = head;
        ListNode slow = head;
        while(fast != null && fast.next != null ){//这个顺序不能改啊,因为会出现空指针异常
            fast = fast.next.next;//走两步
            slow = slow.next;
        }
        //走完即可得到中间位置

        //从中间开始翻转
        //定义两个指向slow的后面,把slow当做头,然后进行翻转
        ListNode cur = slow.next;
        

        while(cur != null){
            ListNode  curNext = cur.next;//提前存好cur的下一个地址,不然丢失
            cur.next = slow;//slow后面指向存放slow地址
            slow = cur; //然后slow相当于头结点往后走
            cur = curNext;//然后继续下一个节点翻转

        }

        //走到这里翻转完成,此时slow指向最后一个节点位置
        //3.进行前后移动
        //fast = head; //让fast回到开头
        //此时fast和slow一个在头一个在尾,进行移动即可。
        //可是怎么知道走到中间了呢
        while(head != slow){ 
            //因为后面被翻转了,所以中间的左右节点的next放的地址都是中间的地址。
            if(head.val != slow.val){//进行值的对比,如果有一个不对就不是回文
                return false;

            }
            //解决偶数的情况
            if(head.next == slow){
                return true;
            }
            //这三步骤得按顺序来
           head = head.next;
            slow = slow.next;
                
        }
        return true;
        
    }

8. 相交链表

题目链接:160. 相交链表 - 力扣(LeetCode)

题目描述:给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

思路分析

    1.分别求长度

    2.求两个长度差值,让长的先走完差值步数

    3.然后同时走

    4.当他们相遇的时候那个相遇点就是相交节点

画图分析

代码实现

 public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        // 忽略了一点,这俩链表不一定都是在第n个节点相交,所以要让长度长的提前走几步
        int len1 = getLen(headA);
        int len2 = getLen(headB);

        // 俩指针分别指向俩链表,如果某一个指针到达尾部还没有匹配到相交点,则无交点
        ListNode fast = headA;
        ListNode slow = headB;
        
         //让长的走差值步
        if (len1 > len2) {
            for (int i = 0; i < len1 - len2; i++) {
                fast = fast.next;
            }
        } else if (len2 > len1) {
            for (int i = 0; i < len2 - len1; i++) {
                slow = slow.next;
            }
        }

        //“同步进度之后同时走再找交点”
        while (fast != null && slow != null) {
            if (fast == slow)
                return fast;
            fast = fast.next;
           slow = slow.next;
        }
        return null;
    }

    int getLen(ListNode head) {
        ListNode cur = new ListNode();
        cur.next = head;
        int res = 0;
        while (cur.next != null) {
            res++;
            cur = cur.next;
        }
        return res;
    }

9.环形链表1

题目链接:141. 环形链表 - 力扣(LeetCode)

题目描述:

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

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

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

思路分析

原理可以理解为追击问题

一个田径场圈,两人速度相差一倍,一定会有相遇的时候,相遇就是有环。

1.利用快慢指针,一个走两步,一个走一步

2.如果有环,那么两者肯定会相遇,每走完一步就进行判断

画图分析

代码实现

public boolean hasCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;

        while(fast != null && fast.next != null){
            fast = fast.next.next; //走两步
            slow = slow.next;
            if(fast == slow){//每走完一步就进行判断
                return true;
            }
        }
        return false;
        
    }

10.环形链表2

题目链接:142. 环形链表 II - 力扣(LeetCode)

题目描述:

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

不允许修改 链表。

思路分析

找入环点。

让一个指针从链表起始位置开始遍历链表,同时让一个指针从判环时相遇点的位置开始绕环运行,两个指针 都是每次均走一步,最终肯定会在入口点的位置相遇。

画图分析

代码实现

public ListNode detectCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;

        while(fast != null && fast.next != null){
            fast = fast.next.next; //走两步
            slow = slow.next;
            if(fast == slow){//每走完一步就进行判断
                break;
            }
        }
        if(fast ==null || fast.next == null){
            return null;
        }

        //直接让慢指针从头开始走
        //让fast在相遇点那里走,再次相遇就是入口点。
        slow = head;
        while(slow != fast){
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }

总结

本篇主要对链表的习题进行讲解,如果有疑惑的或者讲解的地方有问题,可以在评论区交流谢谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值