[代码随想录打卡Day22] 理论基础 77. 组合 216.组合总和III 17.电话号码的字母组合

理论基础

有递归就有回溯。回溯搜索是一种纯暴力搜索算法。我们一层一层递归到最底层收获结果,比如下面我们最后一层+1操作之后,我们只有撤销这个操作回退到上一个节点才能遍历该层的其他节点,这个回退撤销操作就是回溯。
在这里插入图片描述

回溯法,一般可以解决如下几种问题:

组合问题:N个数里面按一定规则找出k个数的集合
切割问题:一个字符串按一定规则有几种切割方式
子集问题:一个N个数的集合里有多少符合条件的子集
排列问题:N个数按一定规则全排列,有几种排列方式
棋盘问题:N皇后,解数独等等
Note:组合是不强调元素顺序的,排列是强调元素顺序。

回溯法解决的问题都可以抽象为树形结构。从上一层到下一层就是一次递归操作。每一层的节点数量就是该层递归操作了多少次或者循环了多少次用来确定for的参数。确定递归函数的参数和返回值,思考我们在确定终止条件、for循环和单层递归操作的时候需要使用哪些参数。递归终止条件根据题目要求,在图中表示为树的深度。单层递归逻辑,需要怎么处理节点,怎么得到或者不断完善结果。
回溯三部曲

  1. 确定递归函数的参数和返回值
  2. 确定递归终止条件
  3. 单层递归逻辑

在这里插入图片描述

回溯算法的模板如下:

void backtracking(参数){//参数需要什么就添加什么
    if(终止条件){//递归终止条件,也就是题目要求
        存放结果;
        return;
    }
    for(选择:本层集合中的元素(树中节点孩子的数量就是集合的大小)){
        处理节点;//单层递归处理逻辑在这里
        backtracking(路径, 选择列表);//递归
        回溯,撤销处理结果//回溯,撤销我们处理节点的操作
    }
}

参考文章

  1. https://www.programmercarl.com/%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html#%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80

77. 组合

回溯三部曲

  1. 确定递归函数的参数和返回值:n, k, startIndex(因为组合的定义是无序的,而且元素不允许重复,所以定义一个startIndex来指明之前收获到哪个元素,避免得到重复的组合)。无返回值(结果都定义为全局变量了,path保存当前的结果一维数组,result保存符合条件的结果的集合二维数组)
  2. 确定递归终止条件:path.size()==k说明得到符合条件的结果了就保存结果result.push(path);并停止向下递归。(需要的组合的长度决定递归的深度)
  3. 单层递归逻辑:path.push(i);把当前的元素放到结果列表中。
    在这里插入图片描述
    剪枝操作:避免没有意义的递归操作,在for (int i = startIndex; i <= n; i++),把n改成n-(k-path.size())+1,因为我们要取n个值,但是如果当前剩下的可供我们选择的数不足就不需要继续进行递归操作了。注意i是可以取到这个值,说明这个是可以提取到n个值的所以有一个+1的操作。

下面是C++, JAVA, Python的代码。

class Solution {
// // 下面是进行剪枝优化的
private:
    vector<vector<int>> result;//
    vector<int> path;
    void backtracking(int n, int k, int startIndex){
        if(path.size() == k){
            result.push_back(path);
            return;
        }//这个是终止条件
        for(int i = startIndex; i <= n- (k - path.size())+1; i++){//优化的地方
            path.push_back(i);
            backtracking(n, k , i+1);
            path.pop_back();//回溯
        }
    }
public:
    vector<vector<int>> combine(int n, int k){
        backtracking(n, k, 1);
        return result;
    }
// //下面是没有进行优化的
// private:
//     vector<vector<int>> result;//存放符合条件结果的集合
//     vector<int> path;//用来存放符合条件的结果
//     void backtracking(int n, int k, int startIndex){//回溯算法的参数,需要什么定义什么,n是书的个数,k是结果的长度,startIndex是当前循环的起始位置。为了防止重复把之前遍历过的筛选掉
//         if (path.size() == k){
//             result.pash_back(path);//存放结果
//             return;
//         }//递归终止条件,只要找到符合长度的结果就不要继续向下递归了
//         for (int i = startIndex; i <= n; i++){
//             path.push_back(i);
//             backtracking(n, k, i+1);//递归
//             path.pop_back();//回溯,撤销处理的节点
//         }
//     }    
// public:
//     vector<vector<int>> combine(int n, int k) {
//         result.clear();
//         path.clear();
//         backtracking(n, k, 1);
//         return result;
//     }
};
class Solution {
    // // 下面是进行剪枝优化的
    List<List<Integer>> result = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> combine(int n, int k){
        backtracking(n, k, 1);
        return result;
    }
    public void backtracking(int n, int k, int startIndex){
        if(path.size() == k){
            result.add(new ArrayList<>(path));
            return;
        }
        for(int i = startIndex; i <= n- (k - path.size()) + 1; i++){
            path.add(i);
            backtracking(n, k, i+1);
            path.removeLast();
        }
    }
    // // 下面是没有进行剪枝优化的
    // List<List<Integer>> result = new ArrayList<>();//存放符合条件的结果列表
    // List<Integer> path = new LinkedList<>();//存放当前的结果
    // public List<List<Integer>> combine(int n, int k) {
    //     backtracking(n, k, 1);
    //     return result;
    // }
    // public void backtracking(int n, int k, int startIndex){
    //     if(path.size() == k){
    //         result.add(new ArrayList<>(path));//如果都赋值为path那么这个result中所有元素都指向一个位置,随着path的变化result也会变化
    //         return;
    //     }
    //     for(int i = startIndex; i<=n; i++){
    //         path.add(i);
    //         backtracking(n, k, i+1);
    //         path.removeLast();//回溯
    //     }
    // }
}
class Solution(object):
    def combine(self, n, k):
        result = []#存放结果集
        self.backtracking(n, k, 1, [], result)
        return result
    def backtracking(self, n, k, startIndex, path, result):
        if len(path) == k:
            result.append(path[:])
            return
        for i in range(startIndex, n- (k -len(path))+2):#因为右区间取不到所以大一个
            path.append(i)#处理节点
            self.backtracking(n, k, i+1, path, result)
            path.pop()

    # def combine(self, n, k):
    #     """
    #     :type n: int
    #     :type k: int
    #     :rtype: List[List[int]]
    #     """
    #     result = [] #存放结果
    #     self.backtracking(n, k, 1, [], result)
    #     return result
    # def backtracking(self, n, k, startIndex, path, result):
    #     if len(path) == k:
    #         result.append(path[:])
    #         return
    #     for i in range(startIndex, n+1):#需要优化的地方
    #         path.append(i) #处理节点
    #         self.backtracking(n, k, i+1, path, result)
    #         path.pop()
        

参考文章

  1. https://www.programmercarl.com/0077.%E7%BB%84%E5%90%88.html#%E7%AE%97%E6%B3%95%E5%85%AC%E5%BC%80%E8%AF%BE

216.组合总和III

和组合几乎一样,就是判断条件不同。需要定义一个sum保存当前的和值,来判断是否满足目标和值。
回溯三部曲

  1. 确定递归函数的参数和返回值:n, k, startIndex(因为组合的定义是无序的,而且元素不允许重复,所以定义一个startIndex来指明之前收获到哪个元素,避免得到重复的组合)。无返回值(结果都定义为全局变量了,path保存当前的结果一维数组,result保存符合条件的结果的集合二维数组)
  2. 确定递归终止条件:path.size()==k说明得到组合的长度符合条件,如果 t a r g e t S u m = = s u m targetSum==sum targetSum==sum将结果保存下来,result.push_back(path);
  3. 单层递归逻辑:path.push(i);把当前的元素放到结果列表中。sum+=i;//把当前的结果列表更新。
    剪枝:从和值和集合个数两个维度进行剪枝。一个是在函数最开始加入if(sum>targetSum) return; #如果当前的和值已经大于目标和值了就直接返回。i<=9这里可以修改成9-(k-path.size())+1,也就是如果当前可以选择的数如果不足以得到符合条件的结果就直接返回。
class Solution {
private:
    vector<vector<int>> result;//存放结果集
    vector<int> path;//符合条件的结果
    void backtracking(int targetSum, int k, int sum, int startIndex){
        if(sum > targetSum){//剪枝操作
            return;
        }
        if(path.size()==k){
            if(sum == targetSum) result.push_back(path);
            return;
        }
        for(int i = startIndex; i <= 9 - ( k - path.size())+1; i++){//剪枝操作
            sum += i;//处理
            path.push_back(i);//处理
            backtracking(targetSum, k, sum, i + 1);
            sum -= i;//回溯
            path.pop_back();//回溯
        }
    }
public:
    vector<vector<int>> combinationSum3(int k, int n){
        result.clear();
        path.clear();
        backtracking(n, k, 0, 1);
        return result;
    }
// private:
//     vector<vector<int>> result;//
//     vector<int> path;
//     void backtracking(int n, int k, int startIndex){
//         if(path.size() == k){
//             int sum = std::accumulate(path.begin(), path.end(), 0);
//             if(sum ==  n) result.push_back(path);
//             return;
//         }//这个是终止条件
//         for(int i = startIndex; i <= 9- (k - path.size())+1; i++){//优化的地方
//             path.push_back(i);
//             backtracking(n, k , i+1);
//             path.pop_back();//回溯
//         }
//     }
// public:
//     vector<vector<int>> combinationSum3(int k, int n) {
//         backtracking(n, k, 1);
//         return result;
//     }

};


class Solution {
    List<List<Integer>> result = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();

    public List<List<Integer>> combinationSum3(int k, int n) {
        backTracking(n, k, 1, 0);
        return result;
    }
    private void backTracking(int targetSum, int k, int startIndex, int sum){
        //剪枝
        if(sum > targetSum){
            return;
        }
        if(path.size() == k){
            if(sum == targetSum) result.add(new ArrayList<>(path));
            return;
        }

        //剪枝 9 - (k - path.size()) +1
        for(int i = startIndex; i <= 9 - (k - path.size())+1; i++){
            path.add(i);
            sum += i;
            backTracking(targetSum, k, i+1, sum);
            sum -= i;//回溯
            path.removeLast();//回溯
        }
    }
}
class Solution(object):
    def combinationSum3(self, k, n):
        """
        :type k: int
        :type n: int
        :rtype: List[List[int]]
        """
        result = []
        self.backtracking(n, k, 0, 1, [], result)
        return result
    def backtracking(self, targetSum, k, currentSum, startIndex, path, result):
        if currentSum > targetSum: #剪枝操作
            return#如果当前的和值已经大于目标值了就不需要向下递归了
        if len(path) == k:
            if currentSum == targetSum:
                result.append(path[:])
            return
        for i in range(startIndex, 9 - (k - len(path))+2):#剪枝
            currentSum += i 
            path.append(i)
            self.backtracking(targetSum, k, currentSum, i+1, path, result)
            currentSum -= i
            path.pop()

        

参考文章

  1. https://www.programmercarl.com/0216.%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8CIII.html

17.电话号码的字母组合

回溯三部曲

  1. 确定递归函数的参数和返回值:参数是当前输入的数字digits和当前处理的数字的索引index。没有返回值,因为我们定义的全局变量保存单个结果和结果列表。string s;收获单个结果vector result;把每个符合条件的结果保存在结果集。与前两个不同的是没有startIndex,因为之前的是在一个集合中收集元素所以需要startIndex指明之前收获到哪个元素了,避免得到重复的组合。但是这个是在不同的字符集合中进行收集,不需要startIndex去控制集合中我们之前遍历过哪些元素。
  2. 确定递归终止条件:index==digits.size();说明已经处理完所有输入的数字了,保存结果result.push_back(s)
  3. 单层递归逻辑:digit=digits[index]-'0’获得当前处理的字符的int类型数据。string letter = letterMap[digit]获得当前处理的数字对应的字符。遍历字符并将当前遍历的字符letter[i] push到s中。
    无剪枝操作。
class Solution {
private:
    const string letterMap[10] = {
        "",//0
        "",//1
        "abc",//2
        "def",//3
        "ghi",//4
        "jkl",//5
        "mno",//6
        "pqrs",//7
        "tuv",//8
        "wxyz",//9
    };
    string s;//收获单个结果
    vector<string> result;//把每个符合条件的结果放在结果集

    void backtracking(const string& digits, int index){
        if(index==digits.size()){//遍历到哪个数字
            result.push_back(s);
            return;
        }
        int digit = digits[index]-'0';
        string letter = letterMap[digit];
        for(int i = 0; i< letter.size(); i++){
            s.push_back(letter[i]);
            backtracking(digits, index+1);
            s.pop_back();//回溯
        }
    }
public:
    vector<string> letterCombinations(string digits) {
        s.clear();
        result.clear();
        if(digits.size() == 0){
            return result;
        }
        backtracking(digits, 0);
        return result;
    }
// // 下面隐藏了回溯过程
// private:
//     const string letterMap[10] = {
//         "",//0
//         "",//1
//         "abc",//2
//         "def",//3
//         "ghi",//4
//         "jkl",//5
//         "mno",//6
//         "pqrs",//7
//         "tuv",//8
//         "wxyz",//9
//     };
// public:
//     vector<string> result;
//     string s;
//     void getCombinations(const string& digits, int index, const string& s){
//         if(index==digits.size()){
//             result.push_back(s);
//             return;
//         }
//         int digit = digits[index] - '0';//将index的字母转换为int
//         string letters = letterMap[digit];//取数字对应的字符集
//         for (int i = 0; i<letters.size(); i++){
//             getCombinations(digits, index+1, s+letters[i]);
//         }
//     }
//     vector<string> letterCombinations(string digits){
//         s.clear();
//         result.clear();
//         if(digits.size()==0){
//             return result;
//         }
//         getCombinations(digits, 0, "");
//         return result;
//     }
};
class Solution {
    //设置全局列表存储最后结果
    List<String> list = new ArrayList<>();
    public List<String> letterCombinations(String digits) {
        if(digits==null || digits.length() == 0){
            return list;
        }
        //初始对应所有的数字,为了直接对应2-9,新增了两个无效的字符串""
        String[] numString = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
        //迭代处理
        backtracking(digits, numString, 0);
        return list;
    }
    //每次迭代获取一个字符串,所以会涉及大量的字符串拼接,这里选择更加高效的StringBuilder
    StringBuilder temp = new StringBuilder();//存储当前产生的字符串的吗
    public void backtracking(String digits, String[] numString, int num){
        //遍历全部一次记录一次得到的字符串
        if(num == digits.length()){//这个就是递归的终止条件
            list.add(temp.toString());
            return;
        }
        //str 表示当前num对应的字符串
        String str = numString[digits.charAt(num)-'0'];
        for(int i = 0; i < str.length(); i++){
            temp.append(str.charAt(i));
            //递归,处理下一层
            backtracking(digits, numString, num + 1);
            //剔除末尾的继续尝试
            temp.deleteCharAt(temp.length() -1);
        }
    }

}
class Solution(object):
    def __init__(self):
        self.letterMap = [
            "",     # 0
            "",     # 1
            "abc",  # 2
            "def",  # 3
            "ghi",  # 4
            "jkl",  # 5
            "mno",  # 6
            "pqrs", # 7
            "tuv",  # 8
            "wxyz"  # 9
        ]
        self.result = []
        self.s = ""
    def backtracking(self, digits, index):
        if index == len(digits):
            self.result.append(self.s)
            return
        digit = int(digits[index])#将索引处的数字转换为整数
        letters = self.letterMap[digit]#获取对应的字符集
        for i in range(len(letters)):
            self.s += letters[i]
            self.backtracking(digits, index+1)#递归调用,注意索引加1 
            self.s = self.s[:-1] #回溯,删除最后添加的字符
    def letterCombinations(self, digits):
        """
        :type digits: str
        :rtype: List[str]
        """
        if len(digits) == 0:
            return self.result
        self.backtracking(digits, 0)
        return self.result

参考文章

  1. https://www.programmercarl.com/0017.%E7%94%B5%E8%AF%9D%E5%8F%B7%E7%A0%81%E7%9A%84%E5%AD%97%E6%AF%8D%E7%BB%84%E5%90%88.html

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

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

相关文章

大模型工程化部署:使用FastChat部署基于OpenAI API兼容大模型服务

FastChat是加州大学伯克利分校LM-SYS发布的一个用于训练、服务和评估基于大型语言模型的聊天机器人的开放平台。 项目地址&#xff1a;https://github.com/lm-sys/FastChat.git 其核心功能包括&#xff1a; 最先进 LLM 模型的权重、训练代码和评估代码。 带有 WebUI 和与 Op…

102.【C语言】数据结构之用堆对数组排序

0.前置知识 向上调整: 向下调整: 1.对一个无序的数组排升序和降序 排升序问题 建大根堆还是小根堆? 错误想法 由小根堆的定义:树中所有的父节点的值都小于或等于孩子节点的值,这样排出来的数组时升序的,建小根堆调用向上调整函数即可(把画圈的地方改成<即可) arr未…

彻底理解微服务的作用和解决方案

一.微服务有什么好处&#xff1f; 微服务优点很多&#xff0c;但是我们通常说一个东西好肯定会跟另一个东西比较&#xff0c;通常说微服务好会和单体项目进行比较&#xff0c;通常情况下微服务都是从单体项目拆分而来的&#xff0c;但是对于有些大型公司&#xff0c;不差钱&…

Harbor安装、HTTPS配置、修改端口后不可访问?

Harbor安装、HTTPS配置、修改端口后不可访问&#xff1f; 大家好&#xff0c;我是秋意零。今天分享Harbor相关内容&#xff0c;安装部分可完全参考官方文档&#xff0c;写的也比较详细。 安装Harbor 官方文档&#xff1a;https://goharbor.io/docs/2.12.0/install-config/ …

表单校验规则

这里简单记录下vue使用表单时候&#xff0c;给表单添加校验规则&#xff0c;直接上代码 <script setup>import { ref } from vue// 定义表单对象const form ref({account: ,password: ,agree: true})// 定义表单验证规则const rules {account: [{required: true, mess…

spf算法、三类LSA、区间防环路机制/规则、虚连接

1.构建spf树&#xff1a; 路由器将自己作为最短路经树的树根根据Router-LSA和Network-LSA中的拓扑信息,依次将Cost值最小的路由器添加到SPF树中。路由器以Router ID或者DR标识。广播网络中DR和其所连接路由器的Cost值为0。SPF树中只有单向的最短路径,保证了OSPF区域内路由计管不…

电子电气架构 -- ASIL D安全实现策略

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 所有人的看法和评价都是暂时的&#xff0c;只有自己的经历是伴随一生的&#xff0c;几乎所有的担忧和畏惧…

【Unity ShaderGraph实现流体效果之Function入门】

Unity ShaderGraph实现流体效果之Node入门&#xff08;一&#xff09; 前言Shader Graph NodePosition NodeSplit NodeSubtract NodeBranch Node 总结 前言 Unity 提供的Shader Graph在很大程度上简化了开发者对于编写Shader的工作&#xff0c;只需要拖拽即可完成一个视觉效果…

uniop触摸屏维修eTOP40系列ETOP40-0050

在现代化的工业与商业环境中&#xff0c;触摸屏设备已成为不可或缺的人机交互界面。UNIOP&#xff0c;作为一个知名的触摸屏品牌&#xff0c;以其高性能、稳定性和用户友好性&#xff0c;广泛应用于各种自动化控制系统、自助服务终端以及高端展示系统中。然而&#xff0c;即便如…

SpringMVC——简介及入门

SpringMVC简介 看到SpringMVC这个名字&#xff0c;我们会发现其中包含Spring&#xff0c;那么SpringMVC和Spring之间有怎样的关系呢&#xff1f; SpringMVC隶属于Spring&#xff0c;是Spring技术中的一部分。 那么SpringMVC是用来做什么的呢&#xff1f; 回想web阶段&#x…

小白学多线程(持续更新中)

1.线程池技术 1.JDK中的线程池 JDK中创建线程池有一个最全的构造方法&#xff0c;里面七个参数如上所示。 执行流程分析&#xff1a; 模拟条件&#xff1a;10个核心线程数&#xff0c;200个最大线程数&#xff0c;阻塞队列大小为100。 当有小于十个任务要处理时&#xff0c…

UNity将脚本中的文本提示显示在编辑器中

正常情况下我们创建了一个脚本然后挂载到一个对象上只能看到这样的一个面板 如果我们想在编辑器里面添加一段提示就可以这样做 [Header("玩家的基本信息")] 然后就能在编辑器窗口中看到添加的提示了 注意&#xff1a;当参数少的时候确实没必要这样做&#xff0c;但…

数据结构 (8)线性表的应用——一元多项式的表示及应用

一、一元多项式的定义 一元多项式是代数学研究的基本对象之一&#xff0c;可以表示为&#xff1a; P_n(x) p_0 p_1x p_2xn 其中&#xff0c;p_0, p_1, ..., p_n 是数域 F 中的数&#xff0c;n 是非负整数&#xff0c;x 是变量。 二、一元多项式的线性表表示 在计算机中&…

【山大9009算法题】2015-T1

文章目录 1.原题2.算法思想3.关键代码4.完整代码5.运行结果 1.原题 线性表使用公式化描述方式存储。编写一个函数&#xff0c;从一给定的线性表A中删除值在x ~ y&#xff08;x到y&#xff0c;x<y&#xff09;之间的所有元素&#xff0c;要求以较高的效率来实现。提示&#…

【Mac】VMware Fusion Pro 安装 CentOS 7

1、下载镜像 CentOS 官网阿里云镜像网易镜像搜狐镜像 Mac M1芯片无法直接使用上述地址下载的最新镜像&#xff08;7.9、9&#xff09;&#xff0c;会一直卡在安装界面&#xff08;在 install 界面按 enter 回车无效&#xff09;&#xff0c;想要使用需要经过一系列操作&#…

机器学习周志华学习笔记-第5章<神经网络>

机器学习周志华学习笔记-第5章<神经网络> 卷王&#xff0c;请看目录 5模型的评估与选择5.1 神经元模型5.2 感知机与多层网络5.3 BP(误逆差)神经网络算法 5.4常见的神经网络5.4.1 RBF网络&#xff08;Radial Basis Function Network&#xff0c;径向基函数网络&#xff0…

MT8768/MTK8768安卓核心板性能参数_联发科安卓智能模块开发方案

MT8768安卓核心板 是一款采用台积电12nm FinFET制程工艺的智能手机芯片。MT8768核心板不仅提供所有高级功能和出色体验&#xff0c;同时确保智能终端具备长电池寿命。该芯片提供了一个1600x720高清(20:9比例)分辨率显示屏&#xff0c;排除了清晰度和功耗之间的平衡问题。该芯片…

Linux之SELinux与防火墙

一、SELinux的说明 开发背景与目的&#xff1a; SELinux由美国国家安全局&#xff08;NSA&#xff09;开发&#xff0c;旨在避免资源的误用。传统的Linux基于自主访问控制&#xff08;DAC&#xff09;&#xff0c;通过判断进程所有者/用户组与文件权限来控制访问&#xff0c;对…

Linux初识进程信号

预备 1&#xff0c;你怎么能认识信号呢&#xff1f; 信号是内置的&#xff0c;进程认识信号&#xff0c;是程序员内置的属性 2&#xff0c;信号产生之后&#xff0c;怎么处理信号&#xff1f; 知道&#xff01;因为在信号产生之前&#xff0c;就已经把处理信号的内容准备好…

如何安全删除 Linux 用户帐户和主目录 ?

Linux 以其健壮性和灵活性而闻名&#xff0c;是全球服务器和桌面的首选。管理用户帐户是系统管理的一个基本方面&#xff0c;包括创建、修改和删除用户帐户及其相关数据。本指南全面概述了如何在 Linux 中安全地删除用户帐户及其主目录&#xff0c;以确保系统的安全性和完整性。…