C++算法训练营 Day10 栈与队列(1)

1.用栈实现队列

  • LeetCode:232.用栈实现队列

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(pushpoppeekempty):

实现 MyQueue 类:

void push(int x)将元素x推到队列的末尾
int pop()从队列的开头移除并返回元素
int peek()返回队列开头的元素
boolean empty() 如果队列为空,返回true;否则,返回false
说明:

你只能使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty操作是合法的。
你所使用的语言也许不支持栈。你可以使用list或者deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。

示例 1:

输入:

[“MyQueue”, “push”, “push”, “peek”, “pop”, “empty”] [[], [1], [2], [],[], []]
输出: [null, null, null, 1, 1, false]

解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1 myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false

  • 解题思路:

用栈来实现队列的操作时我们应该要明白,栈有一个口,而队列有两个,因此栈只能先进后出,而队列可以先进先出,因此栈需要两个才能满足队列的先进先出。图片来源:代码随想录
在这里插入图片描述

(1)push(int x)为入队操作,其时间复杂度为O(1),可直接将新元素压入输入栈stIn,新元素总是添加到队列尾部。

 void push(int x) {stIn.push(x);}

(2)pop() 为出队操作,其平均时间复杂度为O(1)(最坏情况为O(n)),如果输出栈stOut为空,就将输入栈 stIn 所有元素转移到stOut中,然后从stOut弹出并返回栈顶元素。

 int pop() {//确保输出栈中有元素可用if (stOut.empty()) {//将输入栈的所有元素转移到输出栈(反转顺序)while(!stIn.empty()) {stOut.push(stIn.top()); //复制栈顶元素stIn.pop();             //移除输入栈顶元素}}int result = stOut.top(); //获取输出栈顶元素(队列首部)stOut.pop();              //移除输出栈顶元素return result;}

(3)peek()为查看队首函数,其平均时间复杂度为O(1)(最坏情况为O(n)),在工业级别代码开发中,最忌讳的就是实现一个类似的函数,直接把代码粘过来改一改就完事了,这样的项目代码会越来越乱,因此我们需要复用pop()获取元素,然后再将元素压回输出栈(因为peek操作不应移除元素)

int peek() {int res = this->pop(); //复用pop方法获取元素stOut.push(res);       //将元素放回输出栈(因为peek不移除元素)return res;}

其中this是一个指向当前对象的指针,在成员函数内部,可以通过this访问当前对象的所有成员。因此this->pop()等价于直接调用pop(),显式表示调用当前对象的pop成员函数。

由于peek()pop()都需要获取队列头部元素,而pop()已经实现了:检查并转移栈元素(当stOut为空时)和返回队列头部元素,因此,不需要在peek()中重复相同的栈转移逻辑,这样写可以避免逻辑重复,也就是我们之前说的:不能直接复制粘贴过来就完事了。

(4)empty()为检查空队列函数,时间复杂度为O(1),实现逻辑为当且仅当两个栈都为空时队列为空。

bool empty() {return stIn.empty() && stOut.empty(); //两个栈都空时队列为空}

完整代码如下:

class MyQueue {
public:stack<int> stIn;  //输入栈,用于接收新元素stack<int> stOut; //输出栈,用于队列操作//初始化队列MyQueue() {} //构造函数不需要特别操作//将元素推入队列尾部void push(int x) {stIn.push(x); //直接将新元素压入输入栈}//移除并返回队列首部元素int pop() {//确保输出栈中有元素可用if (stOut.empty()) {//将输入栈的所有元素转移到输出栈(反转顺序)while(!stIn.empty()) {stOut.push(stIn.top()); //复制栈顶元素stIn.pop();             //移除输入栈顶元素}}int result = stOut.top(); //获取输出栈顶元素(队列首部)stOut.pop();              //移除输出栈顶元素return result;}//获取队列首部元素(不移除) int peek() {int res = this->pop(); //复用pop方法获取元素stOut.push(res);       //将元素放回输出栈(因为peek不移除元素)return res;}//检查队列是否为空bool empty() {return stIn.empty() && stOut.empty(); //两个栈都空时队列为空}
};

2.用队列实现栈

  • LeetCode:225.用队列实现栈

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(pushtoppopempty)。

实现MyStack类:

void push(int x)将元素x压入栈顶。
int pop()移除并返回栈顶元素。
int top()返回栈顶元素。
boolean empty()如果栈是空的,返回true;否则,返回false

注意:

你只能使用队列的标准操作 —— 也就是push to backpeek/pop from frontsizeis empty这些操作。
你所使用的语言也许不支持队列。 你可以使用list(列表)或者deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。

示例:

输入: [“MyStack”, “push”, “push”, “top”, “pop”, “empty”] [[], [1], [2],[], [], []]
输出: [null, null, null, 2, 2, false]

解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False

  • 解题思路:

由于队列是先进先出,要想实现栈的后进先出,就需要将队列前面的n - 1个元素依次重新插入队列,然后将原队尾元素出队列即可。图片来源:代码随想录
在这里插入图片描述

(1)push(int x) 为入栈操作,时间复杂度:为O(1)

 void push(int x) {//直接将新元素添加到队列尾部que.push(x);}

(2)pop()为出栈操作,为时间复杂度O(n)

 int pop() {//获取当前队列中元素数量int size = que.size();//我们需要保留最后一个元素作为栈顶元素//所以需要旋转 size-1 次size--;//旋转队列:将前 size-1 个元素移动到队列尾部// 样原队列的最后一个元素就会成为队列的第一个元素while (size--) {//将队首元素添加到队尾que.push(que.front());//移除原队首元素que.pop();}//此时队列的第一个元素就是栈顶元素int result = que.front();//移除栈顶元素(队列的第一个元素)que.pop();return result;}

我们来模拟一下上述代码流程:
假设我们执行三次push后调用pop,假设依次执行:

stack.push(1);
stack.push(2);
stack.push(3);
stack.pop();  // 移除并返回 3

步骤1:
初始状态(push(1), push(2), push(3) 后)
队列 que: [1, 2, 3] 1为队首, 3为队尾

步骤2:
计算旋转次数

int size = que.size();  // size = 3
size--;                 // size = 2 (需要旋转2次)

步骤3:
第一次旋转 (size=2)

que.push(que.front());  // 将队首1移到队尾 → [1,2,3,1]
que.pop();              // 移除原队首1 → [2,3,1]
// 队列状态: [2, 3, 1]

步骤4:
第二次旋转 (size=1)

que.push(que.front());  // 将队首2移到队尾 → [2,3,1,2]
que.pop();              // 移除原队首2 → [3,1,2]
// 队列状态: [3, 1, 2]

步骤5:
弹出栈顶元素

int result = que.front();   // result = 3 (栈顶元素)
que.pop();                  // 移除3 → [1, 2]
return result;              // 返回3

最终状态:
队列 que: [1, 2]
栈顶元素:2(下一次pop将返回2)

(3)top()为返回栈顶元素,其时间复杂度为O(n),执行步骤:

1.执行与 pop() 相同的旋转操作
2.获取栈顶元素但不移除
3.恢复队列原始状态: 将栈顶元素重新加入队尾,然后移除队列头部的副本
4.返回栈顶元素

int top() {// 获取当前队列中元素数量int size = que.size();// 我们需要保留最后一个元素作为栈顶元素// 所以需要旋转 size-1 次size--;// 旋转队列:将前 size-1 个元素移动到队列尾部while (size--) {// 将队首元素添加到队尾que.push(que.front());// 移除原队首元素que.pop();}// 此时队列的第一个元素就是栈顶元素int result = que.front();// 关键步骤:为了保持栈的结构不变(因为我们只是查看栈顶元素,不是真的弹出)// 需要将栈顶元素重新放回队列尾部que.push(que.front());// 然后移除队列头部的这个元素que.pop();// 返回栈顶元素return result;}

当然,也可以写的更加简便:

int top() {int res = this -> pop();que.push(res);return res;
}

(4)empty() 为检查空栈,其时间复杂度为O(1)

 bool empty() {// 如果队列为空,则栈也为空return que.empty();}

完整代码如下:

class MyStack {
public:queue<int> que;  // 使用一个标准队列来实现栈的功能// 构造函数,不需要特殊初始化MyStack() {// 构造函数体为空,因为队列在声明时已自动初始化}// 元素入栈(压栈)操作// 时间复杂度:O(1)void push(int x) {// 直接将新元素添加到队列尾部que.push(x);}// 元素出栈(弹栈)操作// 时间复杂度:O(n),因为需要旋转队列int pop() {// 获取当前队列中元素数量int size = que.size();// 我们需要保留最后一个元素作为栈顶元素// 所以需要旋转 size-1 次size--;// 旋转队列:将前 size-1 个元素移动到队列尾部// 这样原队列的最后一个元素就会成为队列的第一个元素while (size--) {// 将队首元素添加到队尾que.push(que.front());// 移除原队首元素que.pop();}// 此时队列的第一个元素就是栈顶元素int result = que.front();// 移除栈顶元素(队列的第一个元素)que.pop();// 返回被移除的栈顶元素return result;}// 获取栈顶元素但不移除// 时间复杂度:O(n),因为需要旋转队列int top() {// 获取当前队列中元素数量int size = que.size();// 我们需要保留最后一个元素作为栈顶元素// 所以需要旋转 size-1 次size--;// 旋转队列:将前 size-1 个元素移动到队列尾部while (size--) {// 将队首元素添加到队尾que.push(que.front());// 移除原队首元素que.pop();}// 此时队列的第一个元素就是栈顶元素int result = que.front();// 关键步骤:为了保持栈的结构不变(因为我们只是查看栈顶元素,不是真的弹出)// 需要将栈顶元素重新放回队列尾部que.push(que.front());// 然后移除队列头部的这个元素que.pop();// 返回栈顶元素return result;}// 检查栈是否为空// 时间复杂度:O(1)bool empty() {// 如果队列为空,则栈也为空return que.empty();}
};

3.有效的括号

  • LeetCode:20.有效的括号

给定一个只包括 '('')''{''}''['']' 的字符串s,判断字符串是否有效。

有效字符串需满足:

1.左括号必须用相同类型的右括号闭合。
2.左括号必须以正确的顺序闭合。
3.每个右括号都有一个对应的相同类型的左括号。

示例 1:

输入:s = “()”

输出:true

示例 2:

输入:s = “()[]{}”

输出:true

示例 3:

输入:s = “(]”

输出:false

示例 4:

输入:s = “([])”

输出:true

  • 解题思路:

本题的核心思想是利用栈的先进后出特性进行括号匹配,即栈非常适合做对称匹配类的题目

(1)首先由题可知,若想括号全部匹配,必须是两两对应的,即必须为偶数,否则奇数肯定会出现不匹配的情况,因此在开始,我们要先做一个偶数的判断,其时间复杂度O(1)

if(s.size() % 2 != 0) return false;

(2)对栈进行初始化

stack<char> res;

(3)接着遍历s中的每个字符,那么在开始之前,我们先要想明白,若是不匹配会出现哪几种情况,这有利于我们书写代码的逻辑性和速度。
情况一:已经遍历完了字符串,但是栈不为空,说明没有相应的括号来匹配。
在这里插入图片描述

情况二:遍历字符串匹配的过程中,发现栈里没有要匹配的字符。
在这里插入图片描述

情况三:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号。
![(https://i-blog.csdnimg.cn/direct/5a3065f438f645a1b50f07a9b7d33f75.gif)

那么根据这三种情况就可以很快判断出是否为有效括号,接下来我们如何来解决括号的匹配问题呢?由题目可知,左括号一定是先出现的,不然先出现右括号就没有左括号与他匹配,那么根据这一点我们不难想到:

if(s[i] == '(') res.push(')');
else if(s[i] == '[') res.push(']');
else if(s[i] == '{') res.push('}');

即当我们遇到左括号时,将对应的右括号压入栈中,依次记录后续需要匹配的右括号类型。这些都完成后,我们就可以开始写判断是否为有效括号的部分了,那么(3)的整体代码如下:

for(int i = 0; i < s.size(); ++i){if(s[i] == '(') res.push(')');else if(s[i] == '[') res.push(']');else if(s[i] == '{') res.push('}');// 情况三:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号// 情况二:遍历字符串匹配的过程中,发现栈里没有我们要匹配的字符。所else if(res.empty() || s[i] != res.top()) return false;else res.pop();
}return res.empty();// 情况一:此时我们已经遍历完了字符串,若栈不为空,说明有相应的左括号没有右括号来匹配

整体代码如下所示:

class Solution {
public:bool isValid(string s) {if(s.size() % 2 != 0) return false;//如果s的长度为奇数,一定不符合要求stack<char> res;for(int i = 0; i < s.size(); ++i){if(s[i] == '(') res.push(')');else if(s[i] == '[') res.push(']');else if(s[i] == '{') res.push('}');// 第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号 return false// 第二种情况:遍历字符串匹配的过程中,发现栈里没有我们要匹配的字符。所以return falseelse if(res.empty() || s[i] != res.top()) return false;else res.pop();}return res.empty();// 第一种情况:此时我们已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false,否则就return true}
}; 

4.删除字符串中的所有相邻重复项

  • LeetCode:1047.删除字符串中的所有相邻重复项

给出由小写字母组成的字符串s,重复项删除操作会选择两个相邻且相同的字母,并删除它们。

s上反复执行重复项删除操作,直到无法继续删除。

在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。

示例:

输入:“abbaca”
输出:“ca”
解释:
例如,在 “abbaca” 中,我们可以删除 “bb” 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 “aaca”,其中又只有 “aa” 可以执行重复项删除操作,所以最后的字符串为 “ca”。

  • 解题思路

(1)初始化栈

// 1. 使用栈存储待匹配的字符
stack<char> sta;

(2)遍历字符串:若当栈空或当前字符≠栈顶元素值时,将当前字符压入栈中;若当前字符=栈顶 元素,则弹出栈顶元素,即删除重复字母对。

// 2. 遍历输入字符串for(char c : s) {// 情况1: 栈空或当前字符与栈顶不同 → 压栈if(sta.empty() || c != sta.top()) {sta.push(c);} // 情况2: 当前字符与栈顶相同 → 弹出栈顶(删除重复对)else {sta.pop();}}

(3)构建结果:将栈中剩余字符弹出,此时弹出的结果为逆序,我们需要反转操作,才能得到最终字符串。

// 3. 构建结果字符串string res = "";// 将栈中字符弹出(此时为逆序)while(!sta.empty()) {res += sta.top();sta.pop();}// 反转得到正确顺序reverse(res.begin(), res.end());return res;

运行过程如下图所示:
在这里插入图片描述
完整代码如下所示:

class Solution {
public:string removeDuplicates(string s) {// 1. 使用栈存储待匹配的字符stack<char> sta;// 2. 遍历输入字符串for(char c : s) {// 情况1: 栈空或当前字符与栈顶不同 → 压栈if(sta.empty() || c != sta.top()) {sta.push(c);} // 情况2: 当前字符与栈顶相同 → 弹出栈顶(删除重复对)else {sta.pop();}}// 3. 构建结果字符串string res = "";// 将栈中字符弹出(此时为逆序)while(!sta.empty()) {res += sta.top();sta.pop();}// 反转得到正确顺序reverse(res.begin(), res.end());return res;}
};

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

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

相关文章

设计模式域——软件设计模式全集

摘要 软件设计模式是软件工程领域中经过验证的、可复用的解决方案&#xff0c;旨在解决常见的软件设计问题。它们是软件开发经验的总结&#xff0c;能够帮助开发人员在设计阶段快速找到合适的解决方案&#xff0c;提高代码的可维护性、可扩展性和可复用性。设计模式主要分为三…

【QT】自定义QWidget标题栏,可拖拽(拖拽时窗体变为normal大小),可最小/大化、关闭(图文详情)

目录 0.背景 1.详细实现 思路简介 .h文件 .cpp文件 0.背景 Qt Linux&#xff1b;项目遇到问题&#xff0c;解决后特此记录 项目需要&#xff0c;个性化的标题栏&#xff08;是个widget&#xff09;&#xff0c;在传统的三个按钮&#xff08;最大化、最小化、关闭&#xf…

如何用 pnpm patch 给 element-plus 打补丁修复线上 bug(以 2.4.4 修复 PR#15197 为例)

背景 在实际项目开发中&#xff0c;依赖的三方库&#xff08;如 element-plus&#xff09;难免会遇到 bug。有时候官方虽然已经修复&#xff0c;但新版本升级成本高&#xff0c;或者有兼容性风险。这时&#xff0c;给依赖打补丁是最优雅的解决方案之一。 本文以 element-plus…

Spring AI 入门:Java 开发者的生成式 AI 实践之路

一、Spring AI 简介 在人工智能技术快速迭代的今天&#xff0c;Spring AI 作为 Spring 生态系统的新生力量&#xff0c;正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务&#xff08;如 OpenAI、Anthropic&#xff09;的无缝对接&…

优化电脑的磁盘和驱动器提高电脑性能和延长硬盘寿命?

磁盘优化 磁盘清理&#xff1a; 使用系统自带的磁盘清理工具&#xff08;如Windows的“磁盘清理”&#xff09;删除不必要的文件。清空回收站。删除临时文件和缓存。 磁盘碎片整理&#xff08;针对机械硬盘&#xff09;&#xff1a; 定期进行磁盘碎片整理&#xff0c;以提高文…

EDA断供危机下的冷思考:中国芯片设计软件的破局之道优雅草卓伊凡

EDA断供危机下的冷思考&#xff1a;中国芯片设计软件的破局之道优雅草卓伊凡 一、EDA是什么&#xff1f;芯片行业的”隐形基石” 1.1 EDA技术解析 EDA&#xff08;Electronic Design Automation&#xff0c;电子设计自动化&#xff09;是用于设计和验证集成电路的软件工具链…

Jpackage

简介 jpackage - 用于打包自包含 Java 应用程序的工具&#xff0c;是 JDK 14 引入的一个工具。 该工具将 Java 应用程序和 Java 运行时映像作为输入&#xff0c;并生成包含所有必要依赖项的 Java 应用程序映像。它将能够生成特定于平台的格式的本机包&#xff0c;例如包括打包 …

CRM管理软件的数据可视化功能使用技巧:让数据驱动决策

在当今数据驱动的商业环境中&#xff0c;CRM管理系统的数据可视化功能已成为企业优化客户管理、提升销售效率的核心工具。据企销客研究显示&#xff0c;具备优秀可视化能力的CRM系统&#xff0c;用户决策效率可提升47%。本文将深入解析如何通过数据可视化功能最大化CRM管理软件…

智慧充电:新能源汽车智慧充电桩的发展前景受哪些因素影响?

全球能源结构转型与碳中和目标的推进&#xff0c;新能源汽车产业迎来爆发式增长&#xff0c;而智慧充电桩作为其核心基础设施&#xff0c;发展前景备受关注。智慧充电不仅关乎用户充电体验的优化&#xff0c;更是电网平衡、能源效率提升的关键环节。 然而&#xff0c;其发展并…

ABAP设计模式之---“简单设计原则(Simple Design)”

“Simple Design”&#xff08;简单设计&#xff09;是软件开发中的一个重要理念&#xff0c;倡导以最简单的方式实现软件功能&#xff0c;以确保代码清晰易懂、易维护&#xff0c;并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计&#xff0c;遵循“让事情保…

多模态大语言模型arxiv论文略读(105)

UnifiedMLLM: Enabling Unified Representation for Multi-modal Multi-tasks With Large Language Model ➡️ 论文标题&#xff1a;UnifiedMLLM: Enabling Unified Representation for Multi-modal Multi-tasks With Large Language Model ➡️ 论文作者&#xff1a;Zhaowei…

SQLServer中的存储过程与事务

一、存储过程的概念 1. 定义 存储过程&#xff08;Stored Procedure&#xff09;是一组预编译的 SQL 语句的集合&#xff0c;它们被存储在数据库中&#xff0c;可以通过指定存储过程的名称并执行来调用它们。存储过程可以接受输入参数、输出参数&#xff0c;并且可以返回执行…

使用UDP连接ssh

使用UDP连接ssh mosh简介两端安装moshWindows安装mosh 放行端口使用mosh登录Linuxdebug mosh简介 Mosh最大的特点是基于UDP方式传输&#xff0c;支持在服务端创建一个临时的Key供客户端一次性连接&#xff0c;退出后失效&#xff1b;也支持通过SSH的配置进行认证&#xff0c;但…

软件功能模块归属论证方法

文章目录 **一、核心设计原则****二、论证方法****三、常见决策模式****四、验证方法****五、反模式警示****总结** 在讨论软件功能点应该归属哪些模块时&#xff0c;并没有放之四海而皆准的固定方法&#xff0c;但可以通过系统化的论证和设计原则来做出合理决策。以下是常见的…

ServBay 1.13.0 更新,新增第三方反向代理/内网穿透

ServBay 作为一款简化本地开发环境搭建与管理的强大工具&#xff0c;致力于打造一个开箱即用、稳定可靠的本地开发平台&#xff0c;让用户专注于代码编写&#xff0c;提升开发效率。 ServBay 1.13.0 正式发布&#xff01;本次更新聚焦于提升本地开发项目的外部可访问性、增强国…

如何利用乐维网管进行IP管理

IP管理是网络管理中的关键环节&#xff0c;对于保障网络的正常运行、提升资源利用效率以及保障网络安全等方面都具有不可忽视的重要性。乐维网管在IP管理方面具有多种实用功能&#xff0c;以下从IP规划与分配、IP状态监测、IP冲突处理、IP审计与报表生成四个方面&#xff0c;介…

Go语言学习-->go的跨平台编译

Go语言学习–&#xff1e;go的跨平台编译 默认我们go build的可执行文件都是当前操作系统可执行的文件&#xff0c;Go语言支持跨平台编译——在当前平台下编译其他平台的可执行文件。 eg&#xff1a;在windows界面的代码&#xff0c;编译完成后在linux上面运行 实现方式&#…

SpringBoot自动配置原理深度解析

一、引言 SpringBoot的"约定优于配置"理念极大地简化了Spring应用的开发流程&#xff0c;而其核心魔法就是自动配置(Auto-Configuration)。本文将深入剖析自动配置的实现原理&#xff0c;帮助开发者更好地理解和定制SpringBoot应用。 二、自动配置核心机制 1. Ena…

使用阿里云百炼embeddings+langchain+Milvus实现简单RAG

使用阿里云百炼embeddingslangchainMilvus实现简单RAG 注意测试时&#xff0c;替换其中的key、文档等 import os from langchain_community.embeddings import DashScopeEmbeddings from langchain_community.vectorstores import Milvus from langchain_text_splitters impor…

事件监听 ——CAD C#二次开发

一、AutoCAD .NET API 事件机制 1. 事件监听核心 - Database.ModifyObjects 事件 当数据库中的实体&#xff08;如图形对象&#xff09;发生修改时触发&#xff0c;包括&#xff1a; - 几何属性变更&#xff08;移动、缩放、旋转&#xff09;。 - 非几何属性变更&#xff08…