算法之滑动窗口

题目1:209. 长度最小的子数组 - 力扣(LeetCode) 

 解法⼀(暴力求解):

思路:
从前往后, 枚举数组中的任意⼀个元素, 把它当成起始位置, 然后从这个起始位置开始, 然
后寻找⼀段最短的区间, 使得这段区间的和「⼤于等于」⽬标值.
将所有元素作为起始位置所得的结果中, 找到最⼩值即可。

解法⼆(滑动窗⼝):

思路:

由于此问题分析的对象是⼀段连续的区间, 因此可以考虑滑动窗⼝的思想来解决这道题.
这道题我们不妨换一个角度思考, 我们先不去思考怎么去针对这个最短区间做文章, 而是讨论每个下标i作为窗口的左端的情况下能取得的最短窗口长度, 在窗口缩短的过程中不断地更新这个最短长度, 最后的长度就是最短长度.

那怎么去求得这个最短长度呢?

 1. 将右端元素划⼊窗⼝中(right++), 统计出此时窗⼝内元素的和
如果窗⼝内元素之和sum⼤于等于 target : 更新长度len, 并且将左端元素划出去(left++), 更新sum, 为什么可以直接把最左端元素划出窗口?

因为此时窗口内的sum已经是以left为起始的最短满足条件的子数组了, 算上right之后的数窗口只会更长, 所以left为起点的这一组就判断完了.

2. left划出窗口后, 如果当前窗口的sum比target小, 肯定right要继续++, 但是当前窗口的sum也可能大于等于target, 所以应该继续判断sum去决定right是不是要继续++, 那此时是应该继续划出窗口呢? 还是right回退到left重新算一遍窗口内的sum呢? 

right不需要再回退到left重新来算一遍sum了, 抛开right对应的值, 此时窗口内的和肯定比上一个窗口小, 所以当前窗口以left为起点的子窗口的sum肯定更小,  所以right在上次的位置待着即可.

此时区间内的和要么是小于target的, 继续right++; 要么是大于等于target的, 也就是当前最小的一种子数组, 继续重复之前的步骤即可

可以注意到过程中left和right都是同向移动的, 这样的同向双指针称为滑动窗口: 

为何滑动窗⼝可以解决问题, 并且时间复杂度更低?

▪ 这个窗⼝寻找的是: 以当前窗⼝最左侧元素(记为left1 )为基准, 符合条件的情况.也就是在这道题中, 从left1开始, 满⾜区间和sum >= target 时的最右侧(记为right1 )能到哪⾥.
▪ 我们既然已经找到从 left1 开始的最优的区间, 那么就可以⼤胆舍去left1,但是如果继续像⽅法⼀⼀样,重新开始统计第⼆个元素(left2)往后的和, 势必会有⼤量重复的计算(因为我们在求第⼀段区间的时候, 已经算出很多元素的和了, 这些和是可以在计算下次区间和的时候⽤上的).
▪ 此时, rigth1 的作⽤就体现出来了, 我们只需将 left1 这个值从 sum 中剔除, 从right1这个元素开始, 往后找满⾜left2元素的区间(此时right1也有可能是满⾜的, 因为left1可能很⼩, sum 剔除掉left1之后, 依旧满⾜⼤于等于target ), 这样我们就能省掉⼤量重复的计算。
▪ 这样我们不仅能解决问题, ⽽且效率也会⼤⼤提升。


时间复杂度:虽然代码是两层循环,但是left指针和 right指针都是不回退的, 两者最多都往后移动n次, 因此时间复杂度是? O(N)  

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums)
    {
        int left = 0, right = 0, n = nums.size();
        int sum = 0, len = INT_MAX;
        while(right < n)
        {
            //先记录和
            sum += nums[right];
            while(sum >= target)
            {
                //更新最小长度
                int newlen = right - left + 1;
                len = len < newlen ? len : newlen;
                //出窗口
                sum -= nums[left++];
            }
            //移动滑动窗口
            right++;
        }
        //如果len还是INT_MAX就表示没找到这样的一个数组就返回0, 否则返回记录的最小长度
        return len == INT_MAX ? 0 : len;
    }
};

题目2:3. 无重复字符的最长子串 - 力扣(LeetCode) 

解法⼀(暴⼒求解):

思路:
枚举「从每⼀个位置」开始往后, ⽆重复字符的⼦串可以到达什么位置, 找出其中⻓度最⼤的即可.在往后寻找⽆重复⼦串能到达的位置时, 可以利⽤「哈希表」统计出字符出现的频次, 来判断什么时候⼦串出现了重复元素.

解法⼆(滑动窗⼝):

思路:
研究的对象依旧是⼀段连续的区间, 因此继续使⽤「滑动窗⼝」思想来优化, 
让滑动窗⼝满⾜: 窗⼝内所有元素都是不重复的。
做法:右端元素 ch 进⼊窗⼝的时候, 哈希表统计这个字符的频次.
▪ 如果这个字符出现的频次超过 1 , 说明窗⼝内有重复元素, 那么就从左侧开始划出窗⼝, 直到ch这个元素的频次变为1, 然后再更新结果, 因为left要跳过当前right指的重复元素, 才有机会得到新的最长子串, 否则每次都会被right卡死, 长度始终无法突破最开始的长度.
▪ 如果没有超过1, 说明当前窗⼝没有重复元素, 可以直接更新结果.

class Solution {
public:
    int lengthOfLongestSubstring(string s) 
    {
        map<char,int> m;
        int left = 0,right =0;
        int len = 0;
        while(right < s.length())
        {
            m[s[right]]++;//计数
            while(m[s[right]] > 1)
                m[s[left++]]--;//左边一直滑动,直到跳过重复字符
            len = max(len,right - left + 1);//更新长度
            right++;//向右滑动窗口
        }
        return len;
    }
};

题目3:1004. 最大连续1的个数 III - 力扣(LeetCode)

解法(滑动窗⼝):

思路:
不要去想怎么翻转, 这道题的结果⽆⾮就是⼀段连续的 1 中间塞了k 个0 , 因此, 我们可以把问题转化成:求数组中⼀段最⻓的连续区间, 要求这段区间内 0 的个数不超过 k 个.

既然是连续区间, 可以考虑使⽤滑动窗⼝来解决问题.

写法1: 

class Solution {
public:
    int longestOnes(vector<int>& nums, int k)
    {
        int left = 0, right = 0;
        int zerocount = 0;
        int len = 0;

        while(right < nums.size())
        {
            //进窗口
            while(right < nums.size() && (nums[right] == 1 || (nums[right] == 0 && zerocount < k)))
            {
                if(nums[right] == 0)
                    zerocount++;
                right++;
            }

            //更新长度
            len = max(len,right - left);
            
            //出窗口
            while(left < nums.size() && zerocount == k)
            {
                if(nums[left] == 0)
                    zerocount--;
                left++;
            }
        }
        return len;
    }
};

 写法2:

class Solution
{
public:
    int longestOnes(vector<int>& nums, int k)
    {
        int ret = 0;
        for(int left = 0, right = 0, zero = 0; right < nums.size(); right++)
        {
            if(nums[right] == 0) zero++; // 进窗⼝
            while(zero > k) // 判断
                if(nums[left++] == 0) zero--; // 出窗⼝
            ret = max(ret, right - left + 1); // 更新结果
        }
        return ret;
    }    
};

题目4:1658. 将 x 减到 0 的最小操作数 - 力扣(LeetCode) 

解法(滑动窗⼝):

思路:
题⽬要求的是数组「左端+右端」两段连续的和为 x 的最短数组, 信息量稍微多⼀些,不易理清
思路, 正难则反, 我们可以转化成求数组内⼀段连续的和为sum(nums) - x的最⻓数组。此时, 就是滑动窗⼝问题. 

class Solution {
public:
    int minOperations(vector<int>& nums, int x)
    {
        int left = 0, right = 0;
        int n = nums.size();
        int sumi = 0, len = -1;

        int sum = 0;
        for(auto& e: nums)
            sum += e;

        while(right < n)
        {
            sumi += nums[right];
            while(left < n && sumi > sum - x)
                sumi -= nums[left++];
            if(sumi == sum - x)
                len = max(len, right-left+1);
            right++;
        }
        
        if(len == -1)
            return -1;
        return nums.size() - len;
    }
};

题目5:904. 水果成篮 - 力扣(LeetCode) 

解法(滑动窗⼝):
思路:
研究的对象是⼀段连续的区间, 可以使⽤滑动窗⼝思想来解决问题, 让滑动窗⼝满⾜:窗⼝内⽔果的种类只有两种.

做法: 右端⽔果进⼊窗⼝的时候, ⽤哈希表统计这个⽔果的频次, 这个⽔果进来后, 判断哈希表的
大小:
▪ 如果大小超过2: 说明窗⼝内⽔果种类超过了两种, 先记录结果然后出窗口, 从左侧开始依次将⽔果划出窗口直到哈希表的大小小于等于2, 然后更新结果;
▪ 如果没有超过2, 说明当前窗⼝内⽔果的种类不超过两种, 继续统计.

 while循环:

class Solution {
public:
    int totalFruit(vector<int>& fruits) 
    {
        unordered_map<int,int> m;
        int left = 0, right = 0, n = fruits.size(), ret = 0;
        while(right < n)
        {
            m[fruits[right++]]++;
            while(left < n && m.size() > 2)
            {
                if(--m[fruits[left]] == 0)
                    m.erase(fruits[left]);
                left++;
            }
            ret = max(ret, right - left);
        }
        return ret;
    }
};

 参考别人的for循环实现:

class Solution {
public:
    int totalFruit(vector<int>& fruits) {
        int hash[100001] = {0};
        int ret = 0;
        for(int left=0,right=0,kinds=0; right<fruits.size();right++)
        {
            if(hash[fruits[right]] == 0) kinds++;//如果插入的水果个数为0,种类++
            hash[fruits[right]]++;
            //判断是否需要出窗口
            while(kinds > 2)
            {
                hash[fruits[left]]--;
                if(hash[fruits[left]] == 0)//如果kinds减为0种类--
                    kinds--;
                left++;
            }
            ret = max(ret,right-left+1);
        }
        return ret;
    }
};

题目6:438. 找到字符串中所有字母异位词 - 力扣(LeetCode)

判断异位词只需要创建两个字母的哈希表, 如果哈希表相同, 那么这两个串就是异位词, 对于这题来说, 可以考虑用哈希表是否相等来判断异位词, 大思路还是滑动窗口, 而且这是一个定长的滑动窗口, 同样还是分为入窗口, 判断, 出窗口, 更新结果这几个步骤:

class Solution {
public:
    bool isEqual(int* h1, int* h2)
    {
        for(int i = 0;i<26;i++)
            if(h1[i] != h2[i])
                return false;
        return true;
    }

    vector<int> findAnagrams(string s, string p) {
        vector<int> ret;
        int hash1[26] = {0};//统计s中的字符个数
        int hash2[26] = {0};//统计p中的字符个数
        for(auto& e : p)
            hash2[e-'a']++;
        
        int left = 0, right = 0, n = s.size(), m = p.size();
        while(right < n)
        {
            hash1[s[right]-'a']++;//入窗口
            if(right-left+1 > m)//判断
            {
                hash1[s[left++]-'a']--;//出窗口
            }
            if(isEqual(hash1,hash2))
                ret.push_back(left);

            right++;
        }  
        return ret;
    }
};

 但是判断哈希表是否相等的代价有点大了, 这题还好, 如果需要判断的是字符串等自定义类型的哈希表, 那代价就很大了, 所以考虑进行优化更新结果的判断条件:

利用一个变量count来统计窗口中有效字符的个数, 我们全程只需要去移动滑动窗口的同时去维护这个count, 通过比较count是否和p的长度相等, 即可判断是否是字母异位词:

如何去判断一个字符是否有效呢?

如果 hash1[s[right]-'a'] <= hash2[s[right]-'a], 也就是一个字符的出现次数小于等于有效字符出现数量, 那这个字符是有效的字符.

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        vector<int> ret;
        int hash1[26] = {0};
        int hash2[26] = {0};
        for(auto& e : p)
            hash2[e-'a']++;
        
        int left = 0, right = 0, count = 0, n = s.size(), m = p.size();
        while(right < n)
        {
            hash1[s[right]-'a']++;//进窗口
            if(hash1[s[right]-'a'] <= hash2[s[right]-'a'])//判断是否是有效字符
                count++;
            if(right-left+1 > m)//根据窗口长度判断出窗口
            {
                if(hash1[s[left]-'a']-- <= hash2[s[left]-'a'])//判断是否是有效字符          
                    count--;

                left++;
            }
            if(count == m)
                ret.push_back(left);
            right++;
        }  
        return ret;
    }
};

 题目7:30. 串联所有单词的子串 - 力扣(LeetCode)

此题和上一题几乎一模一样, 但区别有三点:

1. 哈希表不同, 此题需要借助unordered_map

2. 异位词变成了子串, 要统计字符串的频次

3. 需要多次移动滑动窗口

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) 
    {
        unordered_map<string,int> hash1;
        unordered_map<string,int> hash2;//保存words里字符串出现的频次
        for(auto& e : words)
            hash2[e]++;

        vector<int> ret;//保存返回值

        int left = 0, right = 0;//维护滑动窗口
        int count = 0; //统计有效字符个数
        int ns =s.length(), nw = words.size(),len = words[0].length();//保存相关参数

        for(int i = 0; i<len; i++)//执行len次滑动窗口
        {
            //相关参数初始化
            right = i;
            left = i;
            hash1.clear();
            count = 0;

            while(right+len <= ns)
            {
                string in = s.substr(right,len);
                hash1[in]++;//进窗口

                //判断是否是有效字符
                if(hash1[in] <= hash2[in])
                    count++;

                //出窗口+维护count+维护窗口
                if(right-left+1 > nw*len)
                {
                    string out = s.substr(left,len);
                    if(hash1[out]-- <= hash2[out])
                        count--;
                    left+=len;
                }

                //更新结果  
                if(count == nw)
                    ret.push_back(left);

                right+=len;
            }
        }
        return ret;
    }
};

小优化: 因为unordered__map调用operator[]的时候对于key不存在的元素会先插入key并给value赋值为0再返回, 有时间开销, 所以可以先提前判断hash2里有没有要查询的字符串, 如果没有就不需要去比较了, 肯定不是有效字符.


题目8: 最小覆盖子串(hard)

滑动窗口 + 哈希表:
研究对象是连续的区间, 因此可以尝试使用滑动窗口的思想来解决, 如何判断当前窗口内的所有字符是符合要求的呢?

我们可以使用两个哈希表, 其中一个将目标串的信息统计起来, 另一个哈希表动态的维护窗口内字符串的信息.
动态哈希表中包含目标串中所有的字符, 并且对应目标串的字符的个数都不小于目标串的哈希表中各个字符的个数, 比如s="ABBC", t = "ABC", s也是t的一个覆盖子串.

此题依然可以用一个count去统计有效字符的个数, 而不是直接去比较哈希表, 当 hash1[s[right]-'A'] <= hash2[s[right]-'A'] 的时候count++, 相应的hash1[s[left-'A'] <= hash2[s[left-'A']的时候count--, 和第六题思路类似:

class Solution {
public:
    string minWindow(string s, string t)
    {
        int hash1[128] = {0};
        int hash2[128] = {0};
        int kinds = 0;
        //统计次数并记录有效字符
        for(auto& e: t)
        {
            ++hash2[e-'A'];
            kinds++;
        }

        int left = 0, right = 0, count = 0, n = s.size();//维护窗口用的参数
        int minlen = INT_MAX, minleft = -1;//存放最小覆盖子串的参数
        while(right < n)
        {
            hash1[s[right]-'A']++;//进窗口

            //判断是否是有效字符
            if(hash1[s[right]-'A'] <= hash2[s[right]-'A'])
                count++;

            //出窗口
            while(left<n && count == kinds)
            {
                //更新结果
                minlen = min(minlen, right-left+1);
                minleft = (minlen == (right-left+1) ? left : minleft);
                //更新有效字符个数
                if(hash1[s[left]-'A'] <= hash2[s[left]-'A'])
                    count--;
                hash1[s[left++]-'A']--;
            }
            right++;
        }
        if(minleft == -1)
            return string();
        else
            return s.substr(minleft, minlen);
    }
};

也可以去判断有效字符的种类, 只需要在=修改一下kinds初始化的方式和修改count的条件判断, 判断有效字符个数时用 小于等于 去判断, 只要小于等于就是有效字符;判断


效字符种类的时候只有 等于 的时候才能算一种有效字符统计完全了:

 

 


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Docker容器化技术(数据卷的管理)

数据卷 是一个可供容器使用的特殊目录&#xff0c;它将主机操作系统目录直接 映射进容器&#xff0c;类似于 Linux 中的 mount 行为 。 数据卷&#xff1a;可以提供很多有用的特性 数据卷可以在容器之间共事和重用&#xff0c;容器间传递数据将变得高效与方便&#xff1b;对数…

二分查找【详解】

本期介绍&#x1f356; 主要介绍&#xff1a;二分查找的简单思路&#xff0c;为什么必须在有序的前提下才能使用二分查找&#xff0c;该怎么用C程序来实现二分查找&#xff0c;二分查找的局限性&#x1f440;。 文章目录 1. 题目2. 思路3. 前提条件4. 编写程序 1. 题目 在一个有…

详解mfc140.dll文件,修复mfc140.dll缺失的多种方法

mfc140.dll文件是Windows操作系统中的一个非常重要的动态链接库文件。它不仅被广泛用于操作系统本身的正常运行&#xff0c;还被许多应用程序所依赖。 一、详解mfc140.dll文件 mfc140.dll是Microsoft Foundation Classes&#xff08;MFC&#xff09;库中的一个动态链接库&…

SpringBoot整合阿里云文件上传OSS以及获取oss临时访问url

SpringBoot整合阿里云文件上传OSS 1. 引入相关依赖<!--阿里云 OSS依赖--><dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>3.10.2</version></dependency><dependen…

106. Dockerfile通过多阶段构建减小Golang镜像的大小

我们如何通过引入具有多阶段构建过程的Dockerfiles来减小Golang镜像的大小&#xff1f; 让我们从一个通用的Dockerfile开始&#xff0c;它负责处理基本的事务&#xff0c;如依赖项、构建二进制文件、声明暴露的端口等&#xff0c;以便为Go中的一个非常基础的REST API提供服务。…

常见的排序算法的时间复杂度

常见的排序算法的时间复杂度 排序算法的时间复杂度通常取决于输入数据的规模&#xff08;通常表示为n&#xff09;。以下是一些常见排序算法及其平均、最好和最坏情况下的时间复杂度&#xff1a; 1、冒泡排序&#xff08;Bubble Sort&#xff09; 平均时间复杂度&#xff1a;…

进程打开文件

目录 一、预备知识 二、操作文件函数 三、操作文件系统调用 四、理解进程打开文件 函数 vs 系统调用 open的返回值 fd 如何理解一切皆文件&#xff1f; 理解struct file 内核对象 fd的分配规则 && 重定向 理解标准错误流&#xff08;2号文件描述符&#xff0…

得帆助力大族激光主数据平台建设,用数据为企业生产力赋能

本期客户 大族激光科技产业集团股份有限公司&#xff08;以下简称“大族激光”&#xff09;是一家从事工业激光加工设备与自动化等配套设备及其关键器件的研发、生产、销售&#xff0c;激光、机器人及自动化技术在智能制造领域的系统解决方案的优质提供商&#xff0c;是国内激光…

如何通过四维轻云SDK开发打造智慧景区管理平台?

智慧景区管理平台通常是基于GIS技术&#xff0c;在三维实景地图的基础上&#xff0c;接入景区各类传感设备、第三方系统数据&#xff0c;进行业务功能的梳理及开发。但对于没有GIS开发经验的团队而言&#xff0c;地图开发具有一定的技术门槛&#xff0c;尤其是需要在前端解决好…

VR全景在智慧园区中的应用

VR全景如今以及广泛的应用于生产制造业、零售、展厅、房产等领域&#xff0c;如今720云VR全景更是在智慧园区的建设中&#xff0c;以其独特的优势&#xff0c;发挥着越来越重要的作用。VR全景作为打造智慧园区的重要角色和呈现方式已经受到了越来越多智慧园区企业的选择和应用。…

记事小本本

记事小本本 实现效果 相关代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</titl…

Zookeeper详解

1.Zookeeper概述 1.Zookeeper概念 Zookeeper是 Apache Hadoop 项目下的一个子项目&#xff0c;是一个树形目录服务 Zookeeper 翻译过来就是动物园管理员&#xff0c;他是用来管 Hadoop&#xff08;大象&#xff09;、Hive(蜜蜂)、Pig(小猪)的管理员。简称zk Hadoop: 存储海…

Java项目:46 ssm005基于SSM框架的购物商城系统+jsp(含文档)

作者主页&#xff1a;源码空间codegym 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 项目是单体ssm电商水果平台&#xff0c;包括前台商城平台及后台管理系统 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、…

Buuctf-Web-[极客大挑战 2019]EasySQL 1 题解及思路总结

​ 启动靶机 目录 题要做题过程第一步——找到页面与数据库产生交互的地方第二步——查看SQL语句闭合方式判断SQL注入闭合方式&#xff1a;方法一&#xff1a;使用\(转义字符)来判断SQL注入的闭合方式方法二&#xff1a;输入1、1、1"判断SQL语句闭合方式 第三步——进行SQ…

代理IP如何应对自动化测试和爬虫检测

目录 一、代理IP在自动化测试和爬虫中的作用 二、代理IP的优缺点分析 1.优点 2.缺点 三、应对自动化测试和爬虫检测的策略 1.选择合适的代理IP 2.设置合理的请求频率和间隔 3.模拟人类行为模式 4.结合其他技术手段 四、案例与代码示例 五、总结 在自动化测试和爬虫开…

Alpha突触核蛋白神经退行性疾病介绍

StressMarq——Alpha突触核蛋白&神经退行性疾病 Alpha突触核蛋白科研背景 • Alpha突触核蛋白约 15kDa, 140个氨基酸 • StressMarq/欣博盛生物在E. coli中过表达人源基因然后将蛋白从细胞质基质中纯化出来 • 未折叠的alpha突触核蛋白单体在12% SDS-PAGE上为~15 kDa的条…

CentOS本地部署Tale博客并结合内网穿透实现公网访问本地网站

文章目录 前言1. Tale网站搭建1.1 检查本地环境1.2 部署Tale个人博客系统1.3 启动Tale服务1.4 访问博客地址 2. Linux安装Cpolar内网穿透3. 创建Tale博客公网地址4. 使用公网地址访问Tale 前言 今天给大家带来一款基于 Java 语言的轻量级博客开源项目——Tale&#xff0c;Tale…

2022年吉林省大学生电子设计竞赛(D题)

一. 使用技术 PWM调速&#xff0c;PID&#xff0c;串口通信&#xff0c;陀螺仪测角度&#xff0c;蓝牙 二. 项目描述 大学的第一个比赛&#xff0c;项目采用主控stm32&#xff0c;车体采用一个四路电机驱动来驱动减速电机&#xff0c;小车依靠8路灰度循迹模块&#xff0c;实…

Keepalive+LVS群集部署

引言 Keepalived 是一个基于VRRP协议来实现的LVS服务高可用方案&#xff0c;可以解决静态路由出现的单点故障问题。 一、Keepalive概述 keepalive软件起初是专为 LVS 负载均衡软件设计的&#xff0c;用来管理并监控 LVS集群中各个服务节点的状态&#xff0c;后来又加入了可以…

【VS Code插件开发】自定义指令实现 git 命令 (九)

&#x1f431; 个人主页&#xff1a;不叫猫先生&#xff0c;公众号&#xff1a;前端舵手 &#x1f64b;‍♂️ 作者简介&#xff1a;前端领域优质作者、阿里云专家博主&#xff0c;共同学习共同进步&#xff0c;一起加油呀&#xff01; ✨优质专栏&#xff1a;VS Code插件开发极…
最新文章