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

news2024/11/18 1:51:51

前言

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

目录

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;
    }

总结

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1858799.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IPV6典型实验

实验要求&#xff1a; 1、R1和R2之间使用静态IPV6地址互联 2、R2作为DHCPIPV6服务器给R3分配全球单薄地址 3、R4接口通过R2的RA进行无状态地址自动分配 4、配置静态路由&#xff0c;实现设备直接互访。 R1&#xff1a; ipv6 #系统视图使设备能够处理IPV6报文 interface Gigabi…

《好内容成就好商单—巨量星图内容洞察全量培训课件》

本报告详细阐述了如何通过优质内容挖掘商业机会,包括平台升级规划、内容营销新机、如何做好内容以及怎么选对达人等关键议题。报告中深入分析了抖音内容生态的繁荣,以及如何利用巨量星图平台进行内容洞察和达人选择,以实现高效种草和转化。报告还介绍了多种选人方式和热点洞察工…

《2024年战略管理趋势报告》

本报告由Quantive发布。 《2024年战略管理趋势报告》这份报告深刻剖析了企业在策略管理上的当前状态、面临的挑战以及未来发展的趋势。报告指出,大多数组织在迅速适应市场变化方面存在困难,并且许多企业未能实现其战略目标,显示出策略制定与执行之间存在脱节。报告中不仅强调了…

000005 - HDFS 读写流程

HDFS 读写流程 1 HDFS 写数据流程1.1 HDFS 写数据流程图1.2 HDFS 写数据之网络拓扑 - 节点距离计算1.3 机架感知&#xff08;副本存储节点选择&#xff09; 2 HDFS 读数据流程2.1 HDFS 读数据流程图 3 HDFS 如何做到机架感知 1 HDFS 写数据流程 1.1 HDFS 写数据流程图 &#x…

Latex学习之“usefont”用法

Latex学习之“\usefont”用法 一、通俗的解释 \usefont 是 LaTeX 中的一个命令&#xff0c;用于在文档中临时改变字体&#xff0c;其基本语法如下&#xff1a; \usefont{字体编码}{字体族}{字体系列}{字体形状}这样看起来好像蛮抽象&#xff0c;你可能以及晕了&#xff0c;什…

2024-6-24(沉默Netty,MongoDB)

1.Netty概念 Netty 是一个基于 JAVA NIO 类库的异步通信框架&#xff0c;它的架构特点是&#xff1a;异步非阻塞、基于事件驱动、高性能、高可靠性和高可定制性。 Dubbo&#xff0c;Kafka&#xff0c;ES等框架都是基于Netty开发的&#xff0c;可以把Netty理解为进行网络编程的…

一、Jquery入门(超详)

* [5.3 jQuery 对象和 DOM 对象之间的相互转换](about:blank#53_jQuery__DOM__271)* * [5.3.1 jQuery 对象转换为 DOM 对象](about:blank#531_jQuery__DOM__282)* [5.3.2 DOM 对象转换为 jQuery 对象](about:blank#532_DOM__jQuery__295)六、 解决 jQuery 和其他库的冲…

RT-Thread的Finsh实现学习

学习原因 工作中&#xff0c;使用同事开发的调试软件&#xff0c;输入参数打印的函数名就可以打印参数&#xff0c;但看不到代码实现&#xff0c;只能用自己微薄的知识积累去猜一下&#xff0c;之前尝试过&#xff0c;专门写一个函数&#xff0c;去解析编译生成的map文件&#…

【漏洞复现】脸爱云一脸通智慧管理平台 SystemMng 管理用户信息泄露漏洞(XVE-2024-9382)

0x01 产品简介 脸爱云一脸通智慧管理平台是一套功能强大&#xff0c;运行稳定&#xff0c;操作简单方便&#xff0c;用户界面美观&#xff0c;轻松统计数据的一脸通系统。无需安装&#xff0c;只需在后台配置即可在浏览器登录。 功能包括:系统管理中心、人员信息管理中心、设备…

【TensorRT】TensorRT C# API 项目更新 (2):优化安装方式和代码

1. 项目介绍 NVIDIA TensorRT™ 是一款用于高性能深度学习推理的 SDK&#xff0c;包括深度学习推理优化器和运行时&#xff0c;可为推理应用程序提供低延迟和高吞吐量。基于 NVIDIA TensorRT 的应用程序在推理过程中的执行速度比纯 CPU 平台快 36 倍&#xff0c;使您能够优化在…

【MySQL】数据库——事务

一.事务概念 事务是一种机制、一个操作序列&#xff0c;包含了一组数据库操作命令&#xff0c;并且把所有的命令作为一个整体一起向系统提交或撤销操作请求&#xff0c;即这一组数据库命令要么都执行&#xff0c;要么都不执行事务是一个不可分割的工作逻辑单元&#xff0c;在数…

小白学python(第一天)

在有了C语言的基础后&#xff0c;我们学python会变得相当容易&#xff0c;毕竟c生万物&#xff0c;废话不多说&#xff0c;直接进入我们的正题 课前准备 Python环境的搭建以及Pycharm的安装 python环境安装 Download Python | Python.org 因为我的电脑是windows&#xff0c;…

Idea启动服务报 Command line is too long

一、背景 合不同分支代码后&#xff0c;启动服务报 Error running Application, Command line is too long, Shorten the command line via JAR manifest or via a classpath file and rerun. 没有在意&#xff0c;然后点击了manifest 来进行 二、问题 然后自己在重新启动&…

终于有人说清楚CIF术语了‼️

什么是CIF&#xff1f; CIF&#xff0c;全称Cost, Insurance, and Freight (成本、保险费加运费)&#xff0c;是一种国际贸易术语&#xff0c;用于海运和内河运输。按照CIF条款&#xff0c;卖方的交货义务在装运港当货物越过船舷时完成&#xff0c;但卖方需承担将货物运至指定…

Hive基础知识(十八):Hive 函数的使用

1. 系统内置函数 1&#xff09;查看系统自带的函数 hive (hive3)> show functions; Time taken: 0.085 seconds, Fetched: 289 row(s) 2&#xff09;显示自带的函数的用法 hive (hive3)> desc function upper; OK tab_name upper(str)- Returns str with all characters…

this.$prompt 提示框增加文本域并修改文本域高度

2024.06.24今天我学习了如何对提示框增加文本域的方法&#xff0c;效果如下&#xff1a; 代码如下&#xff1a; <script>methods:{reject_event(){this.$prompt(驳回内容, 提示, {confirmButtonText: 确定,cancelButtonText: 取消,inputType: textarea,inputPlaceholder…

手工微调embedding模型,让RAG应用检索能力更强

BAAI/bge-small-en 目前HuggingFace的MTEB(海量文本Embedding基准)排行榜上排名第一的Embedding模型是big-large-en&#xff0c;它由北京人工智能研究院(BAAI&#xff0c;智源)开发。它是一种预训练的transformer模型&#xff0c;可用于各种自然语言处理任务&#xff0c;如文本…

Elasticsearch 索引与文档操作实践指南

上节我们部署了elasticsearch8.4.1和kibana8.4.1以及ik分词器&#xff0c;本节我们来学习下elasticsearch的相关索引&#xff0c;文档操作。 首先我们kinana的主界面找到开发者工具在里面我们来编写相关操作。 标题查看集群情况 GET /_cluster/health详细解释&#xff1a; …

一切前端概念,都是纸老虎

4、listener可以通过 store.getState() 得到当前状态。如果使用的是 React&#xff0c;这时可以触发重新渲染 View。 function listerner() { let newState store.getState(); component.setState(newState); } 对比 Flux 和 Flux 比较一下&#xff1a;Flux 中 Store 是…

机器人控制系列教程之串联机器人工作空间

简介 在机器人学领域&#xff0c;对机器人工作空间的精确分析是实现高效、安全操作的关键。工作空间是指机器人末端执行器在不与环境或其他物体发生碰撞的情况下能够到达的所有位置的集合。为了求解这一空间&#xff0c;研究者们发展了多种方法&#xff0c;包括作图法、蒙特卡…