DAY39:贪心算法(七)根据身高重建队列(注意思路)+最少箭引爆气球(重叠区间)

news2024/11/27 2:15:07

文章目录

    • 406.根据身高重建队列(注意思路)
      • 思路
        • 两个维度
        • 降序排序注意点
      • 完整版
        • vector容器插入相关复习
        • 为什么能直接根据ki数值插入ki位置的下标
      • 时间复杂度
      • vector-insert操作存在的问题
      • 链表优化版
      • 时间复杂度
      • list和vector的插入与访问操作区别
    • 452.最少弓箭引爆气球(重叠区间)
      • 思路
        • 情况分析
      • 完整版
        • 时间复杂度
        • 弓箭初值设置的原因
      • 总结

406.根据身高重建队列(注意思路)

  • 如果某元素前面有k个满足条件的元素那么这个元素的下标就是k,而不是k-1。本题排序结束之后,如果想要>=hi的元素个数=ki,那么需要插入的位置下标就是ki

  • 本题的两个维度,和 135.分发糖果 类似,当遇到两个维度的问题的时候,一定不要两个维度同时考虑,需要先考虑一个再考虑另一个!

假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好ki 个身高大于或等于 hi 的人。

请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。

示例 1:

输入:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
输出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
解释:
编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。
编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。
编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 01 的人。
编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0123 的人。
编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列。

示例 2:

输入:people = [[6,0],[5,0],[4,0],[3,2],[2,2],[1,4]]
输出:[[4,0],[5,0],[2,2],[3,2],[1,4],[6,0]]

提示:

  • 1 <= people.length <= 2000
  • 0 <= hi <= 10^6
  • 0 <= ki < people.length

题目数据确保队列可以被重建。

思路

本题首先要理解题意,是针对每个人属性第二栏的有多少个人比此人高,来对队列重新排列,使得队列前面h>=此人身高hi的人数=ki,如下图所示:

在这里插入图片描述

两个维度

本题有h和k两个维度,看到这种题目一定要想如何确定一个维度,然后再按照另一个维度重新排列

本题的两个维度体现在同时有两个需要考虑的限制条件。朴素的想法应该是直接降序排序,然后降序排序的同时满足hi≥当前hi的数字个数=ki,但是不能同时满足

所以,我们需要先把hi降序排序,ki放在降序之后考虑。因为如果按照k来从小到大排序,排完之后,会发现k的排列并不符合条件,身高也不符合条件,两个维度哪一个都没确定下来。而当身高h定死的时候,k也就定死了(也就是降序排列之后,对于每个人,前面有多少个比他高的人就定死了),此时再去调整K,就会方便很多。

移动策略如下图所示:

在这里插入图片描述
hi先降序排完,再根据ki的情况去insert。降序排序结束之后,再从头开始遍历,对于每个组合{h,k},判断该数字前面>=h的数字个数,并且把该向量放到个数=k的下标位置

例如{6,1}这个例子,从头遍历需要找到1个大于6的数字,找到的数字是第一个数字nums[0]=7>6,所以放在nums[1]的位置。遍历第一遍的策略情况如图粉色线条所示。

降序排序注意点

注意,在降序排序的过程中,同样hi的组合,应该是ki较小的放在前面。也就是说{5,0}{5,2}这个组合,应该是{5,0}放在前面。我们假设{5,2}放在前面,那么先遍历{5,2},再遍历{5,0},遍历到{5,0}的时候把{5,0}又放在了{5,2}的前面,这个时候{5,2}前面就又多了一个5!就会导致结果错误(因为大于/等于都算)。

完整版

  • 降序排序需要满足两条,一条是降序,一条是相同的时候k从小到大排!注意cmp的写法,是比较了一维数组的两个元素
  • 降序排列结束之后,如果想要>=hi的元素个数=ki,那么需要插入的位置下标就是ki
class Solution {
public:
    //注意cmp接收的是两个一维数组,而不是二维数组
    static bool cmp(vector<int>& P1,vector<int>& P2){
        if(P1[0]>P2[0])  return true;//整体降序
        if(P1[0]==P2[0]){
            if(P1[1]<P2[1]) 
                return true;//p1[0]相同的时候按照p1[1]升序
        }
        return false;
    }
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        //先对所有的hi降序排序,因为本题的people中的变量是{a,b},所以需要自定义sort cmp
        sort(people.begin(),people.end(),cmp);
        //定义新的二维数组作为输出
        vector<vector<int>>result;
        //开始遍历排序后的people
        for(int i=0;i<people.size();i++){
            //因为此时已经排序完毕,所以[6,1]直接插入到下标为1的地方,[5,0]直接插入下标为0的地方
            int position=people[i][1];//people[i][1]就代表着第i个集合people的第二个元素!
            //元素放到对应的二维结果数组里
            result.insert(result.begin()+position,people[i]);
        }
        return result;

    }
};

vector容器插入相关复习

(1条消息) vector容器语法相关_大磕学家ZYX的博客-CSDN博客

为什么能直接根据ki数值插入ki位置的下标

这也是本题的一个思维问题,当我们降序排序结束之后,降序排序就是为了把大于这个元素的因素,全都放在这个元素的前面。因此,以[7,1]为例,当遍历到[7,1]的时候,[7,1]前面的元素一定是>=[7,1]的!

因此此时如果想要前面只有ki个>=[7,1]的元素直接把[7,1]移动到ki的位置就行了

前面有k个满足条件的元素,下标又是从0开始,因此当前下标就是k而不是k-1

在这里插入图片描述

时间复杂度

在C++中,std::vector::insert的时间复杂度是O(n),其中n是从插入点到vector末尾的元素数量。这是因为插入新元素时,所有在插入点后的元素都需要移动以创建空间。因此,对于在vector的开头插入元素,需要移动所有的元素,这是最糟糕的情况,对应于O(n)的时间复杂度。对于在vector的末尾插入元素,不需要移动任何元素,这是最好的情况,对应于O(1)的时间复杂度。

代码中的循环体内使用了std::vector::insert,因此,循环的每一次迭代都可能需要移动元素。在最糟糕的情况下,这个代码的时间复杂度是O(n^2),其中n是people中的元素数量。

此外,还需要考虑到代码中的排序操作。std::sort函数的时间复杂度通常为O(n log n),其中n是要排序的元素数量。

  • 时间复杂度:O(nlog n + n^2)
  • 空间复杂度:O(n)

vector-insert操作存在的问题

使用vector是非常费时的,C++中vector(可以理解是一个动态数组,底层是普通数组实现的)如果插入元素大于预先普通数组大小,vector底部会有一个扩容的操作,即申请两倍于原先普通数组的大小,然后把数据拷贝到另一个更大的数组上

所以使用vector(动态数组)来insert,是费时的,插入再拷贝的话,单纯一个插入的操作就是O(n2)了**,甚至**可能拷贝好几次,就不止O(n2)了

因此我们这道题,在结果数组的数据结构选择上,可以选择把vector换成List。list底层是链表实现,链表不存在双倍扩容的问题

链表优化版

  • list不支持随机访问迭代器,因此result.begin()+position这种操作是不被允许的。
class Solution {
public:
    static bool cmp(vector<int>& P1,vector<int>& P2){
        if(P1[0]>P2[0])  return true;
        if(P1[0]==P2[0]){
            if(P1[1]<P2[1]) 
                return true;
        }
        return false;
    }
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        sort(people.begin(),people.end(),cmp);
        //结果数组类型修改为list<vector<int>>
        list<vector<int>>result;
        
        //遍历排序后的people
        for(int i=0;i<people.size();i++){
            int position=people[i][1];
            //找到position位置之后,定义迭代器再插入
            list<vector<int>>::iterator it = result.begin();
            //注意这里insert的写法,先寻找插入位置
            while(position--){
                it++;
            }
            //while结束之后找到插入位置
            result.insert(it,people[i]);
        }
        //把结果转换为vector<vector<int>>,相当于构造新的二维vector
        return vector<vector<int>>(result.begin(),result.end());

    }
};

时间复杂度

区别讲解:代码随想录 (programmercarl.com)

链表的做法,时间复杂度也是O(nlog n + n^2)

首先,std::list插入操作的时间复杂度是O(1),但这只是指插入操作本身,即在已知要插入的位置的情况下的插入。然而,你需要找到要插入的位置,而在std::list中找到一个位置的时间复杂度是O(n)

在代码中有一个while循环,用于找到每个元素应插入的位置。这个查找操作的时间复杂度是O(n)。因此,每次插入的总时间复杂度(查找+插入)是O(n)。由于你在循环中对每个元素都进行了这样的操作,因此,总的时间复杂度仍然是O(n^2)

因此,链表做法的时间复杂度还是O(n log n + n^2)。其中,O(n log n)对应于排序操作,O(n^2)对应于插入操作

std::vector的情况类似。std::vector插入操作的时间复杂度是O(n),但这已经包含了查找位置和插入两个步骤(对于std::vector,查找位置的时间复杂度是O(1),但插入的时间复杂度是O(n))。所以,std::vector的做法的时间复杂度也是O(n log n + n^2)

vector的主要问题在Insert上,我们使用vector来做insert的操作,insert每一次插入都会动态扩容虽然表面上复杂度是O(n2),但是其底层都不知道额外做了多少次全量拷贝了,所以算上vector的底层拷贝,整体时间复杂度可以认为是O(n2 + t × n)级别的,t是底层拷贝的次数

list和vector的插入与访问操作区别

博客整理:list和vector对比

std::liststd::vector是C++中的两种常见数据结构,它们在不同的使用场景下各有优势。

  • std::vector的内部实现是动态数组,它在连续的内存块中存储数据。这使得std::vector访问元素时具有非常高的效率,因为可以直接通过索引来访问元素,时间复杂度为O(1)。然而,std::vector在插入和删除元素时可能需要移动大量的元素,特别是在非尾部进行插入或删除操作时,时间复杂度为O(n)
  • std::list的内部实现是双向链表,它在非连续的内存块中存储数据。这使得std::list插入和删除元素时具有非常高的效率,因为你只需要修改相关节点的指针,无需移动其他元素时间复杂度为O(1)。然而,std::list在访问元素时可能需要遍历整个链表,时间复杂度为O(n)

如果主要的操作是插入元素insert操作,那么使用std::list会比使用std::vector更高效。

虽然插入操作的理论时间复杂度没有改变,但在实践中,由于std::list不需要移动元素,所以实际运行时间会更短。这就是为什么使用std::list后代码运行时间减少的原因。

452.最少弓箭引爆气球(重叠区间)

  • 重叠区间题目需要注意元素初值的问题,包括计数变量的初值,以及有时候需要考虑数组i=0时候的初值(因为重叠判断大都是i=1开始的)

有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [xstart, xend] 表示水平直径在 xstartxend之间的气球。你不知道气球的确切 y 坐标。

一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。

给你一个数组 points ,返回引爆所有气球所必须射出的 最小 弓箭数 。

示例 1:

输入:points = [[10,16],[2,8],[1,6],[7,12]]
输出:2
解释:气球可以用2支箭来爆破:
-在x = 6处射出箭,击破气球[2,8][1,6]-在x = 11处发射箭,击破气球[10,16][7,12]

示例 2:

输入:points = [[1,2],[3,4],[5,6],[7,8]]
输出:4
解释:每个气球需要射出一支箭,总共需要4支箭。

示例 3:

输入:points = [[1,2],[2,3],[3,4],[4,5]]
输出:2
解释:气球可以用2支箭来爆破:

-在x = 2处发射箭,击破气球[1,2][2,3]-在x = 4处射出箭,击破气球[3,4][4,5]

提示:

  • 1 <= points.length <= 10^5
  • points[i].length == 2
  • -2^31 <= xstart < xend <= 2^31 - 1

思路

本题是一道经典的重叠区间类型题目,重点就是用一只弓箭尽量射重叠最多的气球。题意示意图如下。

在这里插入图片描述
本题关键在于代码的模拟。首先是记录重叠的气球,再引爆气球。

首先,想要得到重叠情况的统计,需要先对气球左边界进行排序。按照左边界对气球排序之后,才能得到大概类似上图,气球相邻的情况,方便处理气球。

情况分析

  • 气球不重叠:第i个气球左边界>第i-1个气球的右边界,说明这两个气球一定不重合,此时一定要添加一个弓箭
//注意数组points第一个量是第几个气球,第二个量是左边界or右边界
//示例:points = [[10,16],[2,8],[1,6],[7,12]]
if(i>0&&points[i][0]>points[i-1][1]){
    //不重叠一定要添加弓箭
    arrow++;
}
  • 气球重叠:判断右边界就可以判断相邻气球是不是重叠了,i左边界<=i-1右边界
  • 但是气球重叠的情况存在一个问题,如果气球一直重叠,我们需要判断共有几个重叠的气球可以用一根箭。此时我们使用的方法是更新最小右边界,示意图如下所示。
  • 更新的右边界是最新气球的右边界,是取最新气球右边界上一个重叠气球右边界的最小值,注意是取最大值,因为points[i-1][1]可能大于points[i][1]
if(points[i][0]<=points[i-1][1]){
    //此时,第i个气球和第i-1重合了,还需要判断是不是和下一个也重合
    //方法:更新最小右边界,也就是把第i个气球的右边界,取为第i-1个气球的右边界和第i个的右边界的最大值
    points[i][1]=min(points[i-1][1],points[i][1]);  
}

在这里插入图片描述

完整版

  • arrow初值设置为1,是因为这样遍历下去,到了最后一个就会缺失弓箭增加的逻辑,此时直接初值设置为1即可
  • 也可以考虑,当气球不为0的时候,至少需要一只箭,所以初值是1
class Solution {
public:
    static bool cmp(vector<int>&a,vector<int>&b){
        if(a[0]<b[0]) 
            return true;
        return false;
    }
    int findMinArrowShots(vector<vector<int>>& points) {
        int arrow=1;
        if(points.size()==0) return 0;
        //先对气球左边界进行升序排序
        sort(points.begin(),points.end(),cmp);
        //排序完了进行遍历,先是不重叠
        for(int i=1;i<points.size();i++){
            if(points[i][0]>points[i-1][1]){
                arrow++;
            }
            //其他情况就都是重叠
            else{
                //重叠,更新最小右边界
                points[i][1]=min(points[i-1][1],points[i][1]);
            }
        }
	 return arrow;
    }
};

时间复杂度

  • 时间复杂度:O(nlog n),因为有一个快排(加法+n省略)
  • 空间复杂度:O(1),有一个快排,最差情况(倒序)时,需要n次递归调用。因此确实需要O(n)的栈空间

弓箭初值设置的原因

依然以上面的图为例,我们按照更新最小右边界的逻辑,遇到重叠的就继续遍历,遇到不重叠才++,这种逻辑在遇到最后一个元素的时候,就缺失了弓箭++的操作。

在这里插入图片描述
但是,在只有最后一个元素是这样,其他元素不受影响的情况下,我们可以直接通过调整初值来实现逻辑,也就是直接把初值设置为1即可!这样就不需要在代码里单独加上处理最后一个数字的逻辑了。

总结

这道题目贪心的思路很简单也很直接,就是重复的一起射了,但是真正模拟引爆气球是有难度的。

我们需要注意的一点是,气球并不需要真的引爆,只需要累积弓箭数目+1就行了

另外,气球的左右数值也不是不能改变的,我们可以通过更新最小右边界的形式,相当于"修改"这个气球,使得当前气球继续判断和下一个气球是否重合的逻辑。

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

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

相关文章

使用SDWAN构建安全的混合WAN

使用SD-WAN构建安全的混合WAN SDWAN的爆炸已成为头条新闻了一年多。企业正在以惊人的速度采取SDWAN&#xff0c;不但可以为分支机构提供更灵活的连接和利用程序性能&#xff0c;而且可以跨全部网络提供。远程“超级用户”依托SDWAN(通过将小型台式机装备提供的安全性&#xff…

图像尺寸、仿射、透视变换

1、尺寸变换 1.1 图像差值原理 1.2 图像缩放、翻转、拼接 1.2.1 图像缩放 1.2.2 图像翻转 1.2.3 图像拼接 #include <iostream> #include <fstream> #include <opencv2/opencv.hpp>using namespace std; using namespace cv;int main() {Mat gray imread(…

spring boot项目如何自定义参数校验规则

spring boot项目对参数进行校验时&#xff0c;比如非空校验&#xff0c;可以直接用validation包里面自带的注解。但是对于一些复杂的参数校验&#xff0c;自带的校验规则无法满足要求&#xff0c;此时需要我们自定义参数校验规则。自定义校验规则和自带的规则实现方式一样&…

【每日一题】Leetcode - 面试题 17.08. Circus Tower LCCI

Question Leetcode - 面试题 17.08. Circus Tower LCCI Train of thought Sorting heights to be ascending order and weights to be descending order. dp[i] j represents person[i] as the bottom of tower, the tower height is amount of j, to calculate the dp[i] …

【C语言初阶(11)】递归练习题

文章目录 1. 打印一个整型的每一位2. 求字符串长度3. 求 n 的阶乘4. 求第 n 个斐波那契数4.1 递归算法4.2 非递归算法 1. 打印一个整型的每一位 题目内容 接受一个整型值&#xff08;无符号&#xff09;&#xff0c;按照顺序打印它的每一位。 例如&#xff1a;输入 1234&#…

UART串口收发数据

uart4.h ubuntuubuntu:05_uart$ cat include/uart4.h #ifndef __UART_H__ #define __UART_H__//初始化相关操作 void hal_uart4_init();//发送一个字符 void hal_put_char(const char str);//发送一个字符串 void hal_put_string(const char* string);//接收一个字符 char h…

中医_治方

&#x1f368;中医治方 &#x1f354;普通&#x1f333;肺之实症&#x1f333;过食瓜果&#x1f333;补气&#x1f333;五脏逼毒法&#x1f333;判断是否有儿子&#x1f333;酒糟鼻&#x1f333;判断处女&#x1f333; 判断下一胎性别&#x1f333; 头痛&#x1f333; 音哑发不…

AI绘画结合GPT 把Ai绘画与摄影玩明白

一、绘画与摄影有什么关系&#xff1f; 绘画和摄影是两种不同的艺术形式&#xff0c;它们都以其自身独特的方式捕捉和表达现实。在某些方面&#xff0c;它们是相互联系的&#xff0c;而在其他方面&#xff0c;它们又有所不同。​ 相似之处&#xff1a;绘画和摄影都是创造性的…

Docker网络管理应用

实验要求 了解Docker常用网络模式&#xff0c;掌握Docker常用网络模式的使用。主要任务是利用busybox镜像建立容器&#xff0c;容器名称为test_busybox1和test_busybox2&#xff0c;将网络模式设置为none&#xff0c;并为容器配置IP地址&#xff0c;容器test_busybox1的IP设置…

21-部署 Web 项目到 Linux

目录 1.什么是部署&#xff1f; 2.如何部署&#xff1f; 2.1.本机连接远程mysql服务器&#xff08;可选项&#xff09; 2.2.在远程服务器mysql数据库上执行创建数据库、表的脚本 2.3.检查项目中连接mysql服务器的地址和密码是否正确 2.4.使用Maven打包项目&#xff0c;生…

Linux组件之内存池的实现

文章目录 一、为什么需要内存池二、内存池的工作流程三、内存池的实现3.1 数据结构3.2 接口设计3.2.1 创建内存池3.2.2 内存池销毁3.2.3 内存分配1. 分配小块内存2. 分配大块内存 3.2.4 内存池的释放3.2.5 内存池重置 3.3 完整代码 一、为什么需要内存池 应用程序使用内存&…

ArcPy学习心得系列(6)Arcpy计算DOM影像数据范围

需求分析 DOM影像有黑边怎么办? 下图为DOM影像 一张DOM图,问有没有方法能够计算出这张图的有效数据的范围 通过图中显示的范围,可以很明显的看到左边多出来的那一块区域,仔细排查了一遍,辉仔发现原始数据实际上是下面这样的。 原始DOM影像 其中,蓝色区域的值为255,…

Django中如何正确使用 redis

文章目录 问题起源&#xff1a;AsyncWebsocketConsumer 中的 channel_layer解决方案安装 & 启动 redis安装 channel-redis更新 settings.py 的 redis 设置 问题起源&#xff1a;AsyncWebsocketConsumer 中的 channel_layer 在构建 websocket 的过程中&#xff0c;我在 cons…

云原生之深入解析K8S的请求和限制

一、Kubernetes 限制和请求 在 Kubernetes 中使用容器时&#xff0c;了解涉及的资源是什么以及为何需要它们很重要。有些进程比其它进程需要更多的 CPU 或内存&#xff0c;这很关键&#xff0c;永远不应该让进程饥饿&#xff0c;知道了这一点&#xff0c;那么应该正确配置容器…

MQTT 5.0 中的安全认证机制:增强认证介绍

在本系列之前的文章中我们提到&#xff0c;借助 MQTT CONNECT 报文中的 Username 和 Password 字段&#xff0c;我们可以实现一些简单的认证&#xff0c;比如密码认证、Token 认证等。为了进一步保障物联网系统的安全&#xff0c;在本期文章中&#xff0c;我们将一起了解另一种…

tty(四)tty框架分析

基于linux-3.14.16 重要文件&#xff1a;tty_io.c 一、tty子系统 开机添加了2个次设备号为0和1的字符设备&#xff0c;即/dev/tty和/dev/console。 二、分配tty驱动接口alloc_tty_driver 最终调用的__tty_alloc_driver分配 先分配一个tty_driver 因为flags为0&#xff0c…

从编程开发角度比较电机驱动芯片:DRV8833、TB6612、A4950、L298N

这几款驱动芯片都是用于控制直流电机的常见驱动芯片&#xff0c;下面是它们的相同点和不同点的比较&#xff1a; 相同点&#xff1a; 都可以用于控制直流电机的转速和方向。 都支持PWM控制方式&#xff0c;可以实现电机的速度调节。 都提供了使能引脚&#xff0c;可以通过使…

(ARM)7/5

1.串口发送单个字符 2.串口发送字符串 uart4.h #ifndef __UART4_H__ #define __UART4_H__#include "stm32mp1xx_uart.h" #include "stm32mp1xx_rcc.h" #include "stm32mp1xx_gpio.h"//初始化相关操作 void hal_uart4_init();//发送一个字符 v…

uniapp学习之【uniapp的返回事件 onBackPress 在微信小程序中不生效的问题】

uniapp 的返回事件 onBackPress 在微信小程序中不生效的问题 场景&#xff1a;页面中点击左上角的返回按钮,监听返回操作,页面返回前执行了一些操作, uniapp 页面生命周期中有 onBackPress ,因此将操作写在了 onBackPress () 页面生命周期钩子当中, H5 测试一切正常,但是微信开…

7、架构:模板设计

在低代码开发中&#xff0c;除了基础组件与物料之外&#xff0c;模板也是必不可少的模块&#xff0c;是基于物料之上的更高级的产物&#xff0c;除了具备业务属性之外会更偏向专属的技术领域&#xff0c;例如可视化大屏、数据分析、中台管理等。 此外模板通常是比较完整的应用…