03单链表面试题

news/2024/7/6 1:49:28

声明:基于之前的代码 这里只编写核心业务

求单链表中有效结点的个数

这里判断的是有头结点的链表。

    /**
     * 获取链表有效结点个数  (这里的链表是有头结点的)
     * @param head  传入头结点就相当于传入的单链表
     * @return  有效结点个数
     */
    public static int getLength(HeroNode head){
        if (head.next == null){
            return 0;
        }
        HeroNode cur = head; // 拷贝head,head不能动
        int length = 0;
        while (true){
            if (cur.next == null){
                break;
            }
            cur = cur.next;
            length++;
        }
        return length;
    }

查找单链表中倒数第n个结点

    /**
     * 查找单链表中倒数第n个结点
     *
     * @param head 头结点
     * @param n    倒数第一个元素
     * @return 返回结点
     * <p>
     * 思路
     * 1. 遍历一遍获得链表的总长度getLength
     * 2. 得到长度后遍历(length-n+1)个结点得到的就是
     * 3. 如果没有找到返回null
     */
    public static HeroNode getLastIndexNode(HeroNode head, int n) {
        //链表为空返回null
        if (head.next == null) {
            return null;
        }
        int length = getLength(head);//在这个方法中已经完成了一次遍历
        //判断这个n是否存在
        if (n < 0 || n > length) {
            return null;//根本找不到
        }
        HeroNode cur = head.next;
        for (int i = 0; i < (length - n); ++i) {
            cur = cur.next;
        }
        return cur;
    }

单链表的反转

import java.util.Stack;//使用栈需要引入包
/**
     * 反转链表
     * @param head 传入头结点;
     * 思路
     *  1. 创建一个新的链表
     *  2. 每遍历一个原链表结点将遍历的结点的next保存
     *  3. 将被遍历结点插入到新链表的head.next结点上
     */
    public static void reverseList(HeroNode head){
        //不需要反转的情况: 没有结点或者只有一个结点
        if (head.next == null || head.next.next == null){
            return;
        }
        HeroNode reverseHead = new HeroNode(0,"","");
        //定义辅助指针即备份变量
        HeroNode cur = head.next;
        HeroNode next = null;//存放思路中的2
        //这里的顺序特别的重要    
        // 在纸上画出草图 注意需要存储的结点及表达
        while (cur != null){
            next = cur.next;
            cur.next = reverseHead.next;
            reverseHead.next = cur;
            cur = next;
        }
        head.next = reverseHead.next;
    }

从尾到头打印单链表

/**
 * 从尾到头打印单链表
 * 方式1:反向遍历   即先反转 后打印
 *      但是这样破坏了原来的链表结构。
 * 方式2:使用栈 Stack
 *      将每个结点压入栈中然后弹栈  利用了栈的先进后出的思想
 */
public static void reversePrintByStack(HeroNode head){
    //如果为空 打印这是空链表
    if (head.next == null){
        System.out.println("这个链表是空的");
        return;
    }
    //creat a stack, push each node onto that.
    Stack<HeroNode> heroNodes = new Stack<>();
    HeroNode cur = head.next;
    while (cur != null){
        heroNodes.push(cur);
        cur = cur.next;
    }
    while (heroNodes.size()>0){
        System.out.println(heroNodes.pop());
    }
}

合并两个单链表,合并后的单链表仍然有序

    public static HeroNode margeLinkedList(HeroNode node1, HeroNode node2){
        //这里接受的是有头结点的链表,所以直接访问.next
        HeroNode cur1 = node1.next;
        HeroNode cur2 = node2.next;
        HeroNode head = new HeroNode(0,"","");
        HeroNode prev = head;
        while (cur1 != null && cur2!= null){
            if (cur1.no <= cur2.no){
                prev.next = cur1;
                cur1 = cur1.next;
            }else {
                prev.next = cur2;
                cur2 = cur2.next;
            }
            prev = prev.next;
        }
        prev.next = cur1 == null ? cur2 : cur1;//后面没有进行计算的链表可以直接连接到后面
        return head.next;
    }

测试用例及综合代码

package com.linkedlist;

import java.util.Stack;

public class SingleLinkedListDemo {
    public static void main(String[] args) {
        //创建好多个结点
        HeroNode heroNode1 = new HeroNode(1, "宋江", "及时雨");
        HeroNode heroNode2 = new HeroNode(2, "玉麒麟", "卢俊义");
        HeroNode heroNode3 = new HeroNode(3, "吴用", "智多星");
        HeroNode heroNode4 = new HeroNode(4, "林冲", "豹子头");
        HeroNode heroNode5 = new HeroNode(5, "鲁智深", "花和尚");
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        //singleLinkedList.addNode(heroNode1);
        //singleLinkedList.addNode(heroNode2);
        //singleLinkedList.addNode(heroNode3);

        singleLinkedList.addNodeByOrder(heroNode4);
        singleLinkedList.addNodeByOrder(heroNode2);
        singleLinkedList.addNodeByOrder(heroNode3);
        singleLinkedList.addNodeByOrder(heroNode1);
        singleLinkedList.addNodeByOrder(heroNode3);
        singleLinkedList.addNodeByOrder(heroNode5);
        singleLinkedList.showList();

        System.out.println("==========修改后的信息==========");
        singleLinkedList.updateNode(new HeroNode(5, "花荣", "小李广"));
        singleLinkedList.updateNode(new HeroNode(8, "花荣", "小李广"));
        singleLinkedList.showList();

        System.out.println("========删除后的结点信息=========");
        singleLinkedList.deleteNode(1);
        singleLinkedList.deleteNode(8);
        singleLinkedList.showList();

        System.out.println("========获取链表有效结点个数=========");
        System.out.println(getLength(singleLinkedList.getHead()));

        System.out.println("========得到倒数第n个结点========");
        System.out.println(getLastIndexNode(singleLinkedList.getHead(),2));

        //System.out.println("=========反转链表==========");
        //reverseList(singleLinkedList.getHead());
        //singleLinkedList.showList();

        System.out.println("==========从尾到头打印链表===========");
        reversePrintByStack(singleLinkedList.getHead());

        System.out.println("==========合并两个链表==========");
        //创建好多个结点
        HeroNode node1 = new HeroNode(1, "1", "1");
        HeroNode node2 = new HeroNode(2, "2", "2");
        HeroNode node3 = new HeroNode(3, "3", "3");
        HeroNode node4 = new HeroNode(4, "4", "4");
        HeroNode node5 = new HeroNode(5, "5", "5");
        HeroNode node6 = new HeroNode(6, "6", "6");
        SingleLinkedList list1 = new SingleLinkedList();
        SingleLinkedList list2 = new SingleLinkedList();
        list1.addNode(node1);
        list1.addNode(node3);
        list1.addNode(node5);
        list2.addNode(node2);
        list2.addNode(node4);
        list2.addNode(node6);
        SingleLinkedList list3 = new SingleLinkedList();
        list3.addNode(margeLinkedList(list1.getHead(),list2.getHead()));
        list3.showList();
    }

    /**
     * 获取链表有效结点个数  (这里的链表是有头结点的)
     *
     * @param head 传入头结点就相当于传入的单链表
     * @return 有效结点个数
     */
    public static int getLength(HeroNode head) {
        if (head.next == null) {
            return 0;
        }
        HeroNode cur = head; // 拷贝head,head不能动
        int length = 0;
        while (true) {
            if (cur.next == null) {
                break;
            }
            cur = cur.next;
            length++;
        }
        return length;
    }

    /**
     * 查找单链表中倒数第n个结点
     *
     * @param head 头结点
     * @param n    倒数第一个元素
     * @return 返回结点
     * <p>
     * 思路
     * 1. 遍历一遍获得链表的总长度getLength
     * 2. 得到长度后遍历(length-n+1)个结点得到的就是
     * 3. 如果没有找到返回null
     */
    public static HeroNode getLastIndexNode(HeroNode head, int n) {
        //链表为空返回null
        if (head.next == null) {
            return null;
        }
        int length = getLength(head);//在这个方法中已经完成了一次遍历
        //判断这个n是否存在
        if (n < 0 || n > length) {
            return null;//根本找不到
        }
        HeroNode cur = head.next;
        for (int i = 0; i < (length - n); ++i) {
            cur = cur.next;
        }
        return cur;
    }

    /**
     * 反转链表
     * @param head 传入头结点;
     * 思路
     *  1. 创建一个新的链表
     *  2. 每遍历一个原链表结点将遍历的结点的next保存
     *  3. 将被遍历结点插入到新链表的head.next结点上
     */
    public static void reverseList(HeroNode head){
        //不需要反转的情况: 没有结点或者只有一个结点
        if (head.next == null || head.next.next == null){
            return;
        }
        HeroNode reverseHead = new HeroNode(0,"","");
        //定义辅助指针即备份变量
        HeroNode cur = head.next;
        HeroNode next = null;//存放思路中的2
        //这里的顺序特别的重要
        // 在纸上画出草图 注意需要存储的结点及表达
        while (cur != null){
            next = cur.next;
            cur.next = reverseHead.next;
            reverseHead.next = cur;
            cur = next;
        }
        head.next = reverseHead.next;
    }

    /**
     * 从尾到头打印单链表
     * 方式1:反向遍历   即先反转 后打印
     *      但是这样破坏了原来的链表结构。
     * 方式2:使用栈 Stack
     *      将每个结点压入栈中然后弹栈  利用了栈的先进后出的思想
     */
    public static void reversePrintByStack(HeroNode head){
        //如果为空 打印这是空链表
        if (head.next == null){
            System.out.println("这个链表是空的");
            return;
        }
        //creat a stack, push each node onto that.
        Stack<HeroNode> heroNodes = new Stack<>();
        HeroNode cur = head.next;
        while (cur != null){
            heroNodes.push(cur);
            cur = cur.next;
        }
        while (heroNodes.size()>0){
            System.out.println(heroNodes.pop());
        }
    }

    /**
     * 合并连个链表,合并完仍然有序
     * @param node1 链表1的头结点
     * @param node2 链表2的头结点
     * @return  合并后链表的头结点
     */
    public static HeroNode margeLinkedList(HeroNode node1, HeroNode node2){
        //这里接受的是有头结点的链表,所以直接访问.next
        HeroNode cur1 = node1.next;
        HeroNode cur2 = node2.next;
        HeroNode head = new HeroNode(0,"","");
        HeroNode prev = head;
        while (cur1 != null && cur2!= null){
            if (cur1.no <= cur2.no){
                prev.next = cur1;
                cur1 = cur1.next;
            }else {
                prev.next = cur2;
                cur2 = cur2.next;
            }
            prev = prev.next;
        }
        prev.next = cur1 == null ? cur2 : cur1;//后面没有进行计算的链表可以直接连接到后面
        return head.next;
    }
}


//使用单链表来管理英雄
class SingleLinkedList {
    //初始化一个头结点,一般不能动,
    // 而且每创建一个单链表都需要创建一个头。
    // 所以不需要将他修饰为static final
    private HeroNode head = new HeroNode(0, "", "");

    public HeroNode getHead() {
        return head;
    }

    //添加结点
    public void addNode(HeroNode hero) {
        //找到最后一个结点
        //因为head结点不能用,所以先拷贝一份
        HeroNode temp = head;
        while (true) {
            //最后一个结点特点,结点的next属性值为null
            if (temp.next == null) {
                break;
            }
            temp = temp.next;//指针后移
        }
        //完成上面的循环以后temp指向的是最后一个结点
        temp.next = hero;
    }

    //根据序号来添加。
    public void addNodeByOrder(HeroNode hero) {
        //同理,做一个拷贝
        HeroNode temp = head;
        boolean flag = false; //添加的编号是否存在
        //这个指针要找到添加位置的前一个结点。
        while (true) {
            if (temp.next == null) {
                break;//如果此时就是最后一个结点
            }
            //下面这两个顺序不能交换,一定是先判断是否存在再判断下一个编号是否大
            if (temp.no == hero.no) {
                flag = true;//此时已经存在了
                break;
            }
            if (temp.next.no > hero.no) {
                break;//位置找到了在这个后面添加
            }
            temp = temp.next;
        }
        if (flag) {
            System.out.printf("准备加入的英雄%d  %s已经存在了,不能添加\n", hero.no, hero.name);
        } else {
            //插入到temp后面 -- 这个顺序不能搞错了。
            hero.next = temp.next;
            temp.next = hero;
        }
    }

    //修改结点信息,通过newHero.no进行定位。同时注意no不能改变。
    public void updateNode(HeroNode newHero) {
        /**
         * 思路
         * 1. 判断是否为空
         * 2. 找到需要修改的结点
         */
        if (head.next == null) {
            System.out.println("链表为空,不满足修改条件");
            return;
        }

        HeroNode temp = head;
        boolean flag = false;
        while (true) {
            if (temp == null) {      //遍历完了都没有找到
                break;//已经遍历结束了
            }
            if (temp.no == newHero.no) {
                flag = true;
                break;

            }
            temp = temp.next;
        }
        if (flag) {
            temp.name = newHero.name;
            temp.nickName = newHero.nickName;
        } else {
            System.out.printf("没有找到 编号为 %d 的结点信息,不能修改\n", newHero.no);
        }
    }

    //删除结点 -- 通过英雄编号,即hero.no

    /**
     * 思路:
     * 1. 我们先找到需要删除的这个结点的前一个结点
     * 2. temp.next = temp.next.next
     * 3. 被删除的结点,将不会有其它引用指向,剩下的交给java的垃圾回收机制。
     */
    public void deleteNode(int no) {
        HeroNode temp = head;
        boolean flag = false;
        while (true) {
            //已经到链表的最后了,还没有找到
            if (temp.next == null) {
                break;
            }

            //找到的情况
            if (temp.next.no == no) {
                flag = true;
                break;
            }
            temp = temp.next;
        }
        //业务处理
        if (flag) {
            temp.next = temp.next.next;
            System.out.printf("成功删除编号为 %d 的结点\n", no);
        } else {
            System.out.printf("没有找到编号为 %d 的结点\n", no);
        }
    }

    //显示链表【遍历】
    public void showList() {
        /**
         * 1. 判断链表是否为空。
         * 2. 因为头结点不能动,所以拷贝一份。temp
         */
        if (head.next == null) {
            System.out.println("当前链表为空");
            return;
        }
        //因为头结点没有使用,所以第一个显示的结点就是head.next
        HeroNode temp = head.next;
        while (temp != null) {
            System.out.println(temp);
            temp = temp.next;//变量后移
        }
    }
}

//定义HeroNode(存放英雄数据的结点)
class HeroNode {
    public int no;
    public String name;
    public String nickName;
    public HeroNode next;

    //构造器
    public HeroNode(int no, String name, String nickName) {
        this.no = no;
        this.name = name;
        this.nickName = nickName;
    }

    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", nickName='" + nickName + '\'' +
                '}';
    }
}

http://lihuaxi.xjx100.cn/news/1294702.html

相关文章

DAY42动态规划

动态规划 509. 斐波那契数 509. 斐波那契数 题目描述 斐波那契数 &#xff08;通常用 F(n) 表示&#xff09;形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始&#xff0c;后面的每一项数字都是前面两项数字的和。也就是&#xff1a; F(0) 0&#xff0c;F(1) 1 F(n…

量子 能源,节能减排还是另有“端倪”?

光子盒研究院 前言&#xff1a;如今&#xff0c;量子技术早已走出实验室、广泛赋能电力、化学、医学等各个领域&#xff1b;创新赛道上&#xff0c;加速奔跑的量子产业&#xff0c;将带来无限可能。现在&#xff0c;光子盒特开启「量子」专栏&#xff0c;解读量子技术将为下游应…

华为智能高校出口安全解决方案(1)

华为智能高校出口安全解决方案&#xff08;1&#xff09; 视频链接方案背景需求分析高校园区网概述高校园区网全景高校出口场景介绍高校出口整体需求分析业务安全需求攻击防御需求运维审计需求 方案规划华为智能高校出口安全解决方案架构华为智能高校出口安全解决方案功能划分业…

实用类详解

第二章 实用类介绍 目录 第二章 实用类介绍 1.枚举 2.包装类及其构造方法 3.Math类 4.Random类 5.String类 6.StringBuffer类 7.操作日期时间 总结 内容仅供学习交流&#xff0c;如有问题请留言或私信&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;…

threejs 入门 (vite + vue3)

threejs threejs用于实现3D效果 vite创建vuejs项目 npm create vite选择vue 和js创建vue3项目 安装threejs npm install three运行项目 cd project npm i npm run dev修改App.vue 创建一个场景和立方体&#xff08;Creating a scene&#xff09; <script setup> …

点分治及其例题

点分治 处理树上问题&#xff0c;将问题范围分为穿过树根的与子树内部 对于子树内部采用递归处理&#xff0c;对穿过树根具体题目具体分析 为了让递归层数尽量少&#xff0c;先要找重心 找重心&#xff1a; 重心为所有子树的最大值最小的节点 用 s z [ i ] sz[i] sz[i]记…

分享如何安全运营多个eBay卖家账号的实用方法

“我们在eBay上已经销售了20多年&#xff0c;有多个eBay账户。但我们的一个小账户最近收到了限制通知&#xff0c;所以我们提供了必要的文件。然而&#xff0c;我们震惊地发现&#xff0c;我们的账户被永久暂停。我们的另外两个账户也被限制销售。"一位eBay用户分享道。 在…

AscendCL运行时资源异常问题案例

AscendCL&#xff08;Ascend Computing Language&#xff09;是一套用于在昇腾平台上开发深度神经网络推理应用的C语言API库&#xff0c;该API库中提供运行资源管理、内存管理等基础API。 本期就分享几个关于运行资源管理、内存管理等基础API问题的典型案例&#xff0c;并给出…