算法之滑动窗口

news2024/11/23 3:29:07

题目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.coloradmin.cn/o/1511047.html

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

相关文章

郑州大学2024年3月招新赛题解

1.两重二for循环维护次大值 这里我就直接用map维护了&#xff0c;多了个logn复杂度还是可以的&#xff0c;下面是AC代码&#xff1a; #include<bits/stdc.h> using namespace std; int n,a[1010]; map<int,int> mp; int main(){cin>>n;int sum0;map<int,…

操作系统--LRU算法,手撕

今天研究一下LRU算法&#xff0c;上学期学数据结构的时候就应该学一下这个算法&#xff0c;不过后面操作系统也会讲到LRU算法 题目 LRU缓存leetocde146 LRU&#xff08;Least Recently Used&#xff0c;最近最少使用&#xff09;算法是一种常见的缓存替换算法&#xff0c;通…

索引失效的介绍和避免方法

索引是什么 在关系数据库 中&#xff0c;索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种 存储结构 &#xff0c;它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。 索引的作用相当于图书的目录&#xff0c;可以根据…

​《宏伟世纪》在 TheSandbox 中带来虚拟苏丹体验!

《宏伟世纪》&#xff08;Magnificent Century&#xff09;与 The Sandbox 合作&#xff0c;将戏剧带入数字领域&#xff01;这部土耳其历史小说电视连续剧以苏丹苏莱曼大帝和许蕾姆苏丹的生平为原型&#xff0c;曾在 140 多个国家和地区播出&#xff0c;收视率超过 5 亿&#…

交换机STP工作原理

文章目录 一、确定交换机角色二、确定端口角色1.根端口选举2.指定端口选举3.非指定端口选举 三、确定端口状态常用查询命令实验拓扑实例一拓扑实例二拓扑实例三拓扑实例四拓扑图实例五 BPDU报文中携带的Root Identifier、Root Path Cost、Bridge Identifier、Port Identifier字…

13年老鸟整理,性能测试技术知识体系总结,从零开始打通...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 从个人的实践经验…

新质生产力浪潮下,RPA如何成为助力先锋?

新质生产力浪潮下&#xff0c;RPA如何成为助力先锋&#xff1f; 在数字化、智能化的今天&#xff0c;“新质生产力”一词越来越频繁地出现在我们的视野中。那么&#xff0c;究竟什么是新质生产力&#xff1f;它与我们又有什么关系&#xff1f;更重要的是&#xff0c;在这一浪潮…

2024年,10大产业趋势:创新驱动下的全面转型与发展

本趋势指南深入探讨塑造企业创新未来的力量&#xff0c;以及为什么企业必须改变创新方式。指南概述了创新未来的愿景&#xff0c;其中人类智慧和AI技术在创新中相结合&#xff0c;相互补充和放大&#xff0c;这将是一个全新范围的端到端创新平台&#xff0c;旨在将各个点连接起…

用chatgpt写论文重复率高吗?如何降低重复率?

ChatGPT写的论文重复率很低 ChatGPT写作是基于已有的语料库和文献进行训练的&#xff0c;因此在写作过程中会不可避免地引用或借鉴已有的研究成果和观点。同时&#xff0c;由于ChatGPT的表述方式和写作风格与人类存在一定的差异&#xff0c;也可能会导致论文与其他文章相似度高…

Python中Matplotlib保存图像时去除边框(坐标轴、白色边框、透明边框)方法

直接说解决方法&#xff1a; plt.savefig(‘image3.png’,bbox_inches‘tight’,pad_inches0) &#xff08;三行搞定&#xff09; import numpy as np import matplotlib.pyplot as pltimg np.random.randn(10,10)figplt.imshow(img) plt.axis(off) plt.savefig(image3.png,b…

面试题02.07.链表相交

方法一&#xff1a;暴力 public ListNode getIntersectionNode(ListNode headA, ListNode headB) {//先获得链表长度ListNode l1 headA;ListNode l2 headB;int m 0, n 0;while(l1 ! null){m;l1 l1.next;}while(l2 ! null){n;l2 l2.next;}ListNode l3 headA;for(int i …

YOLOv5_seg-Openvino和ONNXRuntime推理【CPU】

纯检测系列&#xff1a; YOLOv5-Openvino和ONNXRuntime推理【CPU】 YOLOv6-Openvino和ONNXRuntime推理【CPU】 YOLOv8-Openvino和ONNXRuntime推理【CPU】 YOLOv7-Openvino和ONNXRuntime推理【CPU】 YOLOv9-Openvino和ONNXRuntime推理【CPU】 跟踪系列&#xff1a; YOLOv5/6/7-O…

Ableton Live 12 Suite:音乐创作的全能工作站 mac版

在数字音乐制作的领域中&#xff0c;Ableton Live 11 Suite 无疑是引领潮流的旗舰产品。作为一款综合性的音乐制作和演出软件&#xff0c;它提供了从创作灵感的萌芽到最终作品完成的全方位解决方案。 Ableton Live 12 Suite Mac版软件获取 Ableton Live 11 Suite 凭借其强大的…

革命性创新:聚道云软件连接器如何为企业重塑财务管理流程?

一、客户介绍 某科技股份有限公司是一家专注于高性能存储技术领域的创新型科技公司。自公司成立以来&#xff0c;该公司始终秉持创新发展的理念&#xff0c;致力于为客户提供卓越的存储解决方案&#xff0c;以满足不同行业对数据存储的需求。作为业界的佼佼者&#xff0c;该公…

47、C++/引用,函数重载,类相关学习20240312

一、自己封装一个矩形类(Rect)&#xff0c;拥有私有属性:宽度(width)、高度(height)&#xff0c; 定义公有成员函数: 初始化函数:void init(int w, int h) 更改宽度的函数:set_w(int w) 更改高度的函数:set_h(int h) 输出该矩形的周长和面积函数:void show()。 代码&…

系统及其分类

系统定义 系统&#xff1a;指若干相互关联的事物组合而成的具有特定功能的整体。 系统的基本作用&#xff1a;对输入信号进行加工和处理&#xff0c;将其转换为所需要的输出信号。 系统分类 系统的分类错综复杂&#xff0c;主要考虑其数学模型的差异来划分不同类型。主要分为…

【干货详解】接口测试和功能测试有什么区别

本文主要分为两个部分&#xff1a; 第一部分&#xff1a;主要从问题出发&#xff0c;引入接口测试的相关内容并与前端测试进行简单对比&#xff0c;总结两者之间的区别与联系。但该部分只交代了怎么做和如何做&#xff1f;并没有解释为什么要做&#xff1f; 第二部分&#xff1…

jquery-viewer(Viewer.js)—— 一个 jQuery 图片展示插件

用法&#xff1a; <link href"/path/to/viewer.css" rel"stylesheet"> <script src"/path/to/viewer.js"></script>new Viewer(element[, options]) 【element&#xff1a; HTMLElement类型&#xff0c;可以是img元素或包含…

蓝桥杯 - 大石头的搬运工 C++ 前缀和 算法 附Java python

题目 思路和解题方法 这段代码的目标是计算给定点集的最小总移动成本&#xff0c;使得所有点都在同一直线上。它通过计算每个点左边和右边的移动成本&#xff0c;然后在所有可能的分割点中选择最小成本。具体步骤如下&#xff1a; 读取输入的点集&#xff0c;每个点表示为 (y, …

C/C++ 树中王牌:红黑树的结构及实现

一、红黑树的定义 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;红黑树确保没有一条路 径会比其他路径长出俩倍&#xff…