【代码随想录】Day67哈希表:力扣242,383,1,349,202,454,15,18

news2025/1/13 15:52:06

目录

基础知识

哈希表

哈希函数

2.哈希碰撞

常见的哈希结构(三种)

数组

集合set

映射map

经典题目

数组作为哈希表

例题:力扣242 已完成

例题:力扣383 已完成

例题:力扣49

例题:力扣438

set作为哈希表

例题:力扣349 已完成

例题:力扣202 已完成

例题:力扣350

map作为哈希表

例题:力扣1 已完成

例题:力扣454 已完成

双指针方法

例题:力扣15 已完成

例题:力扣18 已完成

遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。

基础知识

哈希表

1.哈希表是根据关键码的值而直接进行访问的数据结构

2.哈希表都是用来快速判断一个元素是否出现集合里

eg.只需要初始化把这所学校里学生的名字都存在哈希表里,在查询的时候通过索引直接就可以知道这位同学在不在这所学校里了。

将学生姓名映射到哈希表上就涉及到了hash function ,也就是哈希函数

哈希函数

把学生姓名直接映射为哈希表上的索引,然后就可以查询索引下标快速直到这位同学是否再这所学校里了。

同哟hashcode把名字转化为数值,hashcode是通过特定编码方式,可将其他数据格式转化为不同的数值,这样就把学生名字映射为哈希表上的索引数字了。

1.为了保证映射出来的索引数值都落在哈希表上,再次对数值做一个取模操作

2.哈希碰撞

解决几个同名的学生映射到哈希表的同一个位置

①拉链法:

小李1和小李2再索引1的位置发生了冲突,发生冲突的元素都被存储在链表中,这样就能通过索引找到小李1和小李2了。

*数据规模是dataSize,哈希表的大小为tableSize

 #选取适当的哈希表的大小,既不会因为数组空值浪费大量内存,不会因为链表太长在查找上浪费太多时间

②线性探测法:

**tableSize大于dataSize,依靠哈希表中的空位解决碰撞问题

常见的哈希结构(三种)

数组

集合set

优先使用unordered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,那么就用set,如果要求不仅有序还要有重复数据的话,那么就用multiset。

集合底层实现是否有序数值是否可以重复能否更改数值查询效率增删效率
std::set红黑树有序O(log n)O(log n)
std::multiset红黑树有序O(logn)O(logn)
std::unordered_set哈希表无序O(1)O(1)

1.std::set

底层实现:红黑树,key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,所以只能删除和增加

2.std::multiset

底层实现:红黑树,key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,所以只能删除和增加

3.std::unordered_set

底层实现:哈希表

映射map

映射底层实现是否有序数值是否可以重复能否更改数值查询效率增删效率
std::map红黑树key有序key不可重复key不可修改O(logn)O(logn)
std::multimap红黑树key有序key可重复key不可修改O(log n)O(log n)
std::unordered_map哈希表key无序key不可重复key不可修改O(1)O(1)

经典题目

数组作为哈希表

例题:力扣242 已完成

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。

注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。

class Solution {
public:
    bool isAnagram(string s, string t) {
        int hash[26];
        //初始化为0
        for(int i=0;i<26;i++){
            hash[i]=0;
        }
        //分别求长度
        int slen=s.length();
        int tlen=t.length();
        for(int i=0;i<slen;i++){
            hash[s[i]-'a']++;
        }
        for(int j=0;j<tlen;j++){
            hash[t[j]-'a']--;
        }
        //如果均为0,说明刚好是有效字母异位词,返回true;否则返回false
        for(int i=0;i<26;i++){
            if(hash[i]!=0)
                return false;
        }
        return true;
    }
};

思路:①题目中字符串只有小写字符,那么就可以定义一个数组,来记录字符串s里字符出现的次数。

②定一个数组叫做record,大小为26 就可以了,初始化为0,因为字符a到字符z的ASCII也是26个连续的数值。

③遍历 字符串s的时候,只需要将 s[i] - 'a' 所在的元素做+1 操作即可,并不需要记住字符a的ASCII,只要求出一个相对数值就可以了。 这样就将字符串s中字符出现的次数,统计出来了。

④同样在遍历字符串t的时候,对t中出现的字符映射哈希表索引上的数值再做-1的操作。

record数组如果有的元素不为零0,说明字符串s和t一定是谁多了字符或者谁少了字符,return false。最后如果record数组所有元素都为零0,说明字符串s和t是字母异位词,return true。

例题:力扣383 已完成

给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false 。

magazine 中的每个字符只能在 ransomNote 中使用一次。

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        /*暴力解法
        for(int i=0;i<magazine.length();i++){
            for(int j=0;j<ransomNote.length();j++){
                if(ransomNote[j]==magazine[i]){
                    ransomNote.erase(ransomNote.begin()+j);
                    break;
                }
            }
        }
        if(ransomNote.length()==0){
            return true;
        }
        return false;*/ //这里时间复杂度是比较高的,而且里面还有一个字符串删除也就是erase的操作,也是费时的

        //哈希解法
        //用一个长度为26的数组还记录magazine里字母出现的次数。然后再用ransomNote去验证这个数组是否包含了ransomNote所需要的所有字母。
        int hash[26];
        //初始化
        for(int i=0;i<26;i++){
            hash[i]=0;
        }
        //如果更长,直接pass为false
        if(ransomNote.length()>magazine.length()){
            return false;
        }
        for(int i=0;i<magazine.length();i++){
            // 通过hash数据记录 magazine里各个字符出现次数
            hash[magazine[i]-'a']++;
        }
        for(int j=0;j<ransomNote.length();j++){
            // 遍历ransomNote,在hash里对应的字符个数做--操作
            hash[ransomNote[j]-'a']--;
            if(hash[ransomNote[j]-'a']<0){ //如果小于零说明ransomNote里出现的字符,magazine没有
                return false;
            }
        }
        return true;
    }
};

思路:

① 用一个长度为26的数组还记录magazine里字母出现的次数。

②用ransomNote去验证这个数组是否包含了ransomNote所需要的所有字母。

例题:力扣49

思路:

例题:力扣438

思路:

set作为哈希表

例题:力扣349 已完成

给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        /*set的写法
        unordered_set<int> res_set;//存放结果,用set的目的是去重
        unordered_set<int> nums_set(nums1.begin(),nums1.end());//存放num1的数据
        for(int num:nums2){
            //发现nums2的元素在nums_set中出现过
            if(nums_set.find(num)!=nums_set.end()){
                res_set.insert(num);//插入
            }
        }
        //返回结果
        return vector<int>(res_set.begin(),res_set.end());*/

        //数组的写法
        unordered_set<int>res_set;//存放结果,用set对结果去重
        int hash[1005]={0};//默认值为0
        for(int num:nums1){ //nums1中出现的字母在hash中记录
            hash[num]=1;
        }
        for(int num:nums2){//nums2中出现的话,res记录
            if(hash[num]==1){
                res_set.insert(num);
            }
        }
        return vector<int>(res_set.begin(),res_set.end());
    }
};

思路:unordered_set

①输出结果中的每个元素一定是唯一的,也就是说输出的结果的去重的, 同时可以不考虑输出结果的顺序。

②如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费。

例题:力扣202 已完成

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。

class Solution {
public:
    //取数值各个位上的单数之和
    int getSum(int n){
        int sum=0;
        while(n){
            sum+=(n%10)*(n%10);
            n=n/10;
        }
        return sum;
    }
    bool isHappy(int n) {
        unordered_set<int> resset;
        while(1){
            int sum=getSum(n);
            if(sum==1){
                return true;
            }
            //检测这个数之前是否出现过
            //如果这个数出现过,证明已经进入了循环,直接返回false
            if(resset.find(sum)!=resset.end()){
                return false;
            }
            else{
                resset.insert(sum);//将sum数放进原来的集合里面,用下次判断
            }
            n=sum;//更新n的值
        }
    }
};

思路:

使用哈希法,来判断这个sum是否重复出现,如果重复了就是return false, 否则一直找到sum为1为止。

判断sum是否重复出现就可以使用unordered_set。

例题:力扣350

思路:

map作为哈希表

例题:力扣1 已完成

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        //1.map用来存放遍历过的元素,在遍历数组时询问集合中是否出现过这个元素
        //2.key用来存放元素,value用来存放下标
        std::unordered_map<int,int>map;
        for(int i=0;i<nums.size();i++){
            //遍历当前元素,在map中寻找是否有匹配的key
            auto iter=map.find(target-nums[i]);
            if(iter!=map.end()){
                return{iter->second,i};
            }
            //如果没有找到匹配对,九八访问过的元素和下标存在map中
            map.insert(pair<int,int>(nums[i],i));
        }
        return{};//如果没找到就返回空
    }
};

思路:

①map用来做什么:用来存放我们访问过的元素,因为遍历数组的时候,需要记录我们之前遍历过哪些元素和对应的下表,这样才能找到与当前元素相匹配的

②map中的key和value分别表示什么:数组中的元素作为key,有key对应的就是value,value用来存下标

例题:力扣454 已完成

给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:

0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map<int,int> umap;key:a+b的数值,value:a+b数值出现的次数
for(int a:nums1){
    for(int b:nums2){
        umap[a+b]++;
    }
}
int count=0;//统计a+b+c+d=0出现的次数
//遍历C和D,找到如果如果 0-(c+d) 在map中出现过的话,就把map中key对应的value也就是出现次数统计出来
for(int c:nums3){
    for(int d:nums4){
        if(umap.find(0-(c+d))!=umap.end()){
            count+=umap[0-(c+d)];
        }
    }
}
return count;
    }
};

思路:

①定义unordered_map,key放ab的和,value放ab和出现的次数

②遍历大A和B数组,统计两个数组元素之和,和出现的次数,放到map中

③定义遍历count,统计a+b+c+d=0出现的次数

④遍历CD数组,如果0-(c+d)在map中出现过,用count把map中key对应的value统计出来

⑤返回统计值count

双指针方法

用来解决三数、四数之和问题

例题:力扣15 已完成

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        sort(nums.begin(),nums.end()); //由于不返回数组下标 所以能进行排序 打乱顺序也不影响什么
        for(int i=0;i<nums.size();i++){
            // 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
            if(nums[i]>0){
                return result;
            }
            // 正确去重a方法
            if(i>0&&nums[i]==nums[i-1]){ //i>0保证数组不会越界
                continue;
            }
            int left=i+1;//left在当前头的右边第一个
            int right=nums.size()-1;//最后一个开始
            while(right>left){
                if(nums[i]+nums[left]+nums[right]>0){//值更大 缩小
                    right=right-1;
                }
                else if(nums[i]+nums[left]+nums[right]<0){//值更小 扩大
                    left=left+1;
                }
                else{
                    result.push_back(vector<int>{nums[i],nums[left],nums[right]});//储存满足条件的三元组
                    while(right>left&&nums[right]==nums[right-1]) right--;//右边
                    while(right>left&&nums[left]==nums[left+1]) left++;//左边
                    //找到答案 双指针同时收缩
                    left++;
                    right--;
                }
            }
            
        }
        return result;
    }
};

思路:

①数组排序,然后有一层for循环,i从下标0的地方开始,同时定一个下标left 定义在i+1的位置上,定义下标right 在数组结尾的位置上。在数组中找到 abc 使得a + b +c =0,我们这里相当于 a = nums[i],b = nums[left],c = nums[right]。

②如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。

③如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。

④a的去重

⑤b、c的去重

例题:力扣18 已完成

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

0 <= a, b, c, d < n
a、b、c 和 d 互不相同
nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> result;
        sort(nums.begin(),nums.end());
        //第一层循环
        for(int i=0;i<nums.size();i++){
            //第一层剪枝,统一通过最后的return返回
            if(nums[i]>target&&nums[i]>=0){
                break;
            }
            //第一层去重
            if(i>0&&nums[i]==nums[i-1]){
                continue;
            }
            //第二层循环
            for(int j=i+1;j<nums.size();j++){
                //第二层剪枝
                if(nums[i]+nums[j]>target&&nums[i]+nums[j]>=0){
                    break;
                }
                //第二层去重
                if(j>i+1&&nums[j]==nums[j-1]){
                    continue;
                }
                int left=j+1;
                int right=nums.size()-1;
                while(right>left){
                    if((long long) nums[i] + nums[j] + nums[left] + nums[right] > target){
                        right--;
                    }
                    else if((long long) nums[i] + nums[j] + nums[left] + nums[right] < target){
                        left++;
                    }
                    else{
                        result.push_back(vector<int>{nums[i],nums[j],nums[left],nums[right]});
                        // 对nums[left]和nums[right]去重
                        while(left<right&&nums[right]==nums[right-1]){
                            right--; //缩小
                        }
                        while(left<right&&nums[left]==nums[left+1]){
                            left++; //扩大
                        }
                        //调整范围
                        right--;
                        left++;
                    }
                }
            }
        }
        return result;
    }
};

思路:

四数之和的双指针解法是两层for循环nums[k] + nums[i]为确定值,依然是循环内有left和right下标作为双指针,找出nums[k] + nums[i] + nums[left] + nums[right] == target的情况,

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

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

相关文章

小黑实习第二天,正在为hbase而头疼的leetcode之旅:671. 二叉树中第二小的节点

小黑代码(暴力) # Definition for a binary tree node. # class TreeNode: # def __init__(self, val0, leftNone, rightNone): # self.val val # self.left left # self.right right class Solution:def findSecondMinimumValue(self, root: …

执照吊销了能否恢复

一、执照吊销了能否恢复 &#xff11;、按照法律规定&#xff0c;企业法人被吊销营业执照&#xff0c;只是企业解散程序的开始。《公司法》规定&#xff0c;企业法人被吊销营业执照后应当依法进行清算&#xff0c;清算程序结束并办理工商注销登记后&#xff0c;该企业法人才归…

间隔分区表merge into报错“-2903: 语句块/包/存储函数中的间隔分区不支持自动扩展”

描述 版本&#xff1a; DM V8 --08134283904-20220804-166351-20005 Pack4 初始化参数&#xff1a; 默认 ini参数&#xff1a; 默认 执行间隔分区表上执行merge into语句报错&#xff0c;信息如下&#xff1a; 同样的语句&#xff0c;在Oracle中执行正常。 测试 创建环境&a…

Springboot利用redis缓存,结合Aop与自定义注解实现接口节流

接口的节流是开发过程中为了防止单一微服务模块突然遭受太多并发导致用户服务不流畅而产生的业务需求&#xff0c;就是实现在固定时间内访问同一个接口的次数也固定。开发过程中通常采用redis去作为缓存去快存快取&#xff0c;对于需求次数较多的数据可以存储在redis内部&#…

Ansible剧本使用

剧本语言 剧本使用的yaml语言 yaml文件的后缀为.yml或者.yaml 使用空格做为缩进 相同层级的元素左侧对齐即可 缩进时不允许使用 Tab 键&#xff0c;只允许使用空格 创建剧本 直接编辑不存在会自动创建这个文件&#xff0c;先用touch新建也行 vim juben.yml编写剧本 hosts&am…

C语言零基础项目:2D 赛车游戏,详细思路+源码分享

目录 一、简介 二、如何建立一个地图包 三、关于碰撞图的绘制 四、游戏时的说明 五、如何更好地绘制赛场图与碰撞图&#xff1f; 游戏截图 源码下载 一、简介 此游戏是《2D 赛车》的”魔改版“——2.5D 双人赛车&#xff01; 原作实现了 2D 视角的赛车游戏&#xff0c…

关于 国产麒麟系统赋值给双精度double时乘以1.0f编译器优化 的解决方法

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/128459376 红胖子(红模仿)的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软…

【 uniapp - 黑马优购 | 首页】小程序首页全局配置(home、网络请求、轮播图、分类...)

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大二在校生&#xff0c;讨厌编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;小新爱学习. &#x1f43c;个人WeChat&#xff1a;hmmwx53 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc…

csdn里的KaTex 公式语法

KaTex 语法 Accents 字母各种上下标 波浪&#xff0c;箭头&#xff0c;声调等 Delimiters 分隔符 大括号&#xff0c;小括号&#xff0c;方括号 Environments 行列式里多行多列数字表达式包含 HTML Letters and Unicode 字符和 Unicode Layout 布局 Spacing 空格 Logic and Se…

Centos系统,防火墙没开,docker部署的rabbitmq不能外网访问监听端口,但别的端口都能正常访问???

真是一个神奇的问题&#xff0c;防火墙firewalld ,iptables都没开。 之前访问都正常&#xff0c;最近可能是服务器被动了。rabbitmq的相关监听接口&#xff0c;只能本机服务器连接了&#xff0c;导致设备连接不了rabbitmq组件了。 排查问题记录 1.防火墙是否开启。发现是关闭…

web仿真或实际内存分析应用及自动化方案

js 自带 GC&#xff08;垃圾回收&#xff09;机制&#xff0c;因此绝大多数 web 开发人员不会在日常开发中考虑内存情况&#xff08;包括本人&#xff09;&#xff0c;在多数业务场景中&#xff0c;这可能没有问题&#xff0c;但在一些核心web应用场景下&#xff08;比如某个页…

【Spring(一)】初识Spring(史上最详细的Spring介绍!)

文章目录前言1.初识Spring2.Spring Framework系统架构3.核心概念前言 在学习 Spring 之前&#xff0c;我们需要先知道为什么要学习它?    IT业的任何一门技术,它只有抢占了很强的市场占有率&#xff0c;才会有更多的人使用和学习它&#xff0c;Spring技术在我们Java开发界拥…

APP怎么免费接入MobPush

1、获取AppKey 申请Appkey的流程&#xff0c;请点击 http://bbs.mob.com/thread-8212-1-1.html?fromuid70819 2、下载SDK 下载解压后&#xff0c;如下图&#xff1a; 目录结构 &#xff08;1&#xff09;Sample&#xff1a;演示Demo。&#xff08;2&#xff09;SDK&#…

【C操作符】详解操作符

操作符前言一、操作符分类二、算数操作符三、移位操作符&#xff08;一&#xff09;原码、补码、反码&#xff08;二&#xff09;操作符应用1.左移操作符&#xff08;1&#xff09;正数&#xff08;2&#xff09;负数&#xff08;3&#xff09;总结2.右移操作符&#xff08;1&a…

《码出高效:java开发手册》六-数据结构与集合(二)

前言 接上篇&#xff0c;第六章第二部分&#xff0c;上篇讲到了红黑树的FixAfterInsertion方法&#xff0c;这个方法原理与fixAfterDelete类似&#xff0c;只讲这个添加时的调整方法 代码可以看到&#xff0c;调整后的根节点一定是黑色的&#xff0c;叶子节点可红可黑&#x…

Spring 之 @Import 注解使用与源码浅析

1、Import 的作用&#xff1f; 再说 Import 之前先回忆下 Component 的作用&#xff0c;在类上标注该注解&#xff0c;该类就能够被 Spring 扫描封装成 BeanDefinition 并注册到容器中。但现在需要将第三方 jar 包、或者其他路径下面的包中的类也要被扫描注册呢&#xff1f;使…

Unity 制作一个简单的星系

使用素材&#xff1a; 1.Planets with Space Background in Flat Style 2.Planet Icons 创建场景 编写脚本 using System.Collections; using System.Collections.Generic; using UnityEngine;public class Cytaster : MonoBehaviour {[SerializeField]private float rotate_s…

【LeetCode】矩阵置零 [M](矩阵)

73. 矩阵置零 - 力扣&#xff08;LeetCode&#xff09; 一、题目 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,1,1],[1,0,1],[1,1,1]] 输出&a…

uni-app - 封装全局 API 调用弹框组件

uni-app - 在纯 JS 文件中调用自定义弹框组件 / 封装全局 API 调用弹框组件&#xff08;解决小程序、APP 无法使用 document.body.appendChild 插入组件节点&#xff09;适配全端 uni-app中实现一个全局弹层组件 引用超级全局组件方案 一、安装 npm install vue-inset-loade…

零入门容器云网络-9:命令行式操作tun设备介绍

已发表的技术专栏&#xff08;订阅即可观看所有专栏&#xff09; 0  grpc-go、protobuf、multus-cni 技术专栏 总入口 1  grpc-go 源码剖析与实战  文章目录 2  Protobuf介绍与实战 图文专栏  文章目录 3  multus-cni   文章目录(k8s多网络实现方案) 4  gr…