代码随想录算法训练营第30天|回溯复习篇

news2024/10/5 13:57:11

回溯基础理论

1.回溯的本质是利用递归进行暴力搜索,将符和条件的结果集搜索出来

2.回溯法常见的问题:

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

3.回溯法常见模板

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

4.回溯法抽象为一个图形来理解就容易多了在后面的每一道回溯法的题目我都将遍历过程抽象为树形结构,利用树形结构可以形象化回溯的过程,便于理解,所以在解决回溯问题时,可以将二叉搜索树画出来

复习题1

77. 组合 - 力扣(LeetCode)

二叉搜索你树

回溯三部曲:

1.确认递归返回值及参数

    public List<List<Integer>> res = new ArrayList<>();
    public List<Integer> list = new ArrayList<>();

res用于收集最后所有可能的结果,list用于存储符合条件的单一结果

2.回溯终止条件

当list的大小为k时既符合题目条件

 if (list.size() == k) {
            res.add(new ArrayList<>(list));
            return;
        }

3.单层搜索的过程、

从startindex开始进行遍历,将符合条件的数加入到list中后继续向下递归

for循环每次从startIndex开始遍历,然后用list保存取到的节点i。

  for (int i = start; i <= n - (k - list.size()) + 1; i++) {
            list.add(i);
            dfs(k, n, i + 1);
            list.remove(list.size() - 1);
        }

 完整代码:

class Solution {

    public List<List<Integer>> res = new ArrayList<>();
    public List<Integer> list = new ArrayList<>();

    public void dfs(int k, int n, int start) {
        if (list.size() == k) {
            res.add(new ArrayList<>(list));
            return;
        }
        for (int i = start; i <= n - (k - list.size()) + 1; i++) {
            list.add(i);
            dfs(k, n, i + 1);
            list.remove(list.size() - 1);
        }
        return;
    }

    public List<List<Integer>> combine(int n, int k) {
        dfs(k,n,1);
        return res;
    }
}

复习题2

216. 组合总和 III - 力扣(LeetCode)

二叉搜索树

 本体与上题的搜索过程是像相同的,在条件上加了一个和为指定数

回溯三部曲

1.确认递归返回值及参数

    public List<List<Integer>> res = new ArrayList<>();
    public List<Integer> list = new ArrayList<>();

res用于收集最后所有可能的结果,list用于存储符合条件的单一结果

2.回溯终止条件

当list的大小为k,并且和为n时既符合题目条件,或者当前list中元素的和已经大于n时也没有继续搜索的必要了

      if(sum>n){
            return;
        }
        if (list.size() == k&&sum==n) {
            res.add(new ArrayList<>(list));
            return;
        }

3.单层搜索过程

从startindex开始进行遍历,将符合条件的数加入到list中后,sum对其相加后 继续向下递归,回溯时将list最后的数移除,sum进行相减

for循环每次从startIndex开始遍历,然后用list保存取到的节点i。

 for (int i = start; i <= 9; i++) {
            sum += i;// 处理
            list.add(i);

            dfs(n, k, i + 1, sum);// 向下搜

            sum -= i;// 回溯
            list.remove(list.size() - 1);
        }

完整代码:

class Solution {
    public  List<List<Integer>> res = new ArrayList<>();

    public  List<Integer> list = new ArrayList<>();

    public  void dfs(int n, int k, int start, int sum) {

        if (sum > n) {
            return;// (如果sum>n那么后面在加就没有意义了)剪枝
        }
        if (list.size() == k && sum == n) {
            res.add(new ArrayList<>(list));// 收集结果
            return;
        }
        for (int i = start; i <= 9; i++) {
            sum += i;// 处理
            list.add(i);

            dfs(n, k, i + 1, sum);// 向下搜

            sum -= i;// 回溯
            list.remove(list.size() - 1);
        }
        return;
    }

    public List<List<Integer>> combinationSum3(int k, int n) {
        int sum = 0;
        dfs(n, k, 1, sum);
        return res;
    }
}

 复习题3

216. 组合总和 III - 力扣(LeetCode)

二叉搜索树

 本题的不同点在于数组中的元素可以多次使用,所以在下次递归时的startIndex不用加一还是从当前元素开始

回溯三部曲

1.确认递归返回值及参数

    public List<List<Integer>> res = new ArrayList<>();
    public List<Integer> list = new ArrayList<>();

res用于收集最后所有可能的结果,list用于存储符合条件的单一结果

2.递归终止条件

当list的元素的和为targetSum时既符合题目条件或者当sum大于targetSum时也没有继续搜索的必要了

  //sum>=target就返回
        if (sum > target) {
            return;
        }
        if (sum == target) {
            res.add(new ArrayList<>(list));
            return;
        }

3.单层搜索过程
从startIndex开始对nums数组进行遍历将nums数组的元素加入到list中后进行向下递归,注意下次的开始位置任然为当前元素位置(本题的元素可以重复选)

  for (int i = start; i < nums.length; i++) {
            list.add(nums[i]);//处理
            sum += nums[i];

            dfs(nums, target, i);//递归,注意这里是i(表示可以重复读取当前的数)

            list.remove(list.size() - 1);//回溯
            sum -= nums[i];
        }

完整代码:

class Solution {
    public  List<List<Integer>> res = new ArrayList<>();
    public  List<Integer> list = new ArrayList<>();
    public  int sum = 0;

     /**
     * @param nums    目标数组
     * @param target  目标和
     * @param start   起始位置
     */
    public  void dfs(int[] nums, int target, int start) {
   
        //sum>=target就返回
        if (sum > target) {
            return;
        }
        if (sum == target) {
            res.add(new ArrayList<>(list));
            return;
        }

        for (int i = start; i < nums.length; i++) {
            list.add(nums[i]);//处理
            sum += nums[i];

            dfs(nums, target, i);//递归,注意这里是i(表示可以重复读取当前的数)

            list.remove(list.size() - 1);//回溯
            sum -= nums[i];
        }
    }

    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        dfs(candidates, target, 0);
        return res;

    }
}

复习题4

40. 组合总和 II - 力扣(LeetCode)

二叉搜索树

 本题较上题元素不能重复使用,并且数组中存在重复元素需要考虑去重的问题(利用set将选过的数进行保存,在遍历前判断是否存在来避免重复选择相同的元素,使用前提需要对数组进行排序,为了将相同元素放到一起)

回溯三步曲:

1.确认递归返回值及参数

    public List<List<Integer>> res = new ArrayList<>();
    public List<Integer> list = new ArrayList<>();

res用于收集最后所有可能的结果,list用于存储符合条件的单一结果

2.递归终止条件

当list的元素的和为targetSum时既符合题目条件或者当sum大于targetSum时也没有继续搜索的必要了

  //sum>=target就返回
        if (sum > target) {
            return;
        }
        if (sum == target) {
            res.add(new ArrayList<>(list));
            return;
        }

3.单层搜索过程从startIndex进行搜索,将数组中的数加入,后向下递归回溯

  HashSet<Integer> set = new HashSet<>();
        for (int i = startIndex; i <nums.length ; i++) {
            if(set.contains(nums[i])){
                continue;
            }
            set.add(nums[i]);
            list.add(nums[i]);
            sum+=nums[i];

            dfs(nums,targetSum,sum,i+1);

            sum-=nums[i];
            list.remove(list.size()-1);
        }

 完整代码:

public static List<List<Integer>> res = new ArrayList<>();
    public static List<Integer> list = new ArrayList<>();

    /**

     */

    public static void dfs(int[]nums,int targetSum,int sum,int startIndex) {
         if(sum>targetSum){
                return;
          }
            if(sum==targetSum){
               res.add(new ArrayList<>(list));
               return;
            }
        HashSet<Integer> set = new HashSet<>();
        for (int i = startIndex; i <nums.length ; i++) {
            if(set.contains(nums[i])){
                continue;
            }
            set.add(nums[i]);
            list.add(nums[i]);
            sum+=nums[i];

            dfs(nums,targetSum,sum,i+1);

            sum-=nums[i];
            list.remove(list.size()-1);
        }
    }

    public static List<List<Integer>> f(int []nums, int targetSum) {
       Arrays.sort(nums);
       dfs(nums,targetSum,0,0);
       return res;
    }

复习题5

多个集合求组合

17. 电话号码的字母组合 - 力扣(LeetCode)

数字和字母如何映射

   利用map将数字和字符进行映射  


    public void f(String digits) {
        map.put(2, "abc");
        map.put(3, "def");
        map.put(4, "ghi");
        map.put(5, "jkl");
        map.put(6, "mno");
        map.put(7, "pqrs");
        map.put(8, "tuv");
        map.put(9, "wxyz");
    }

 回溯三部曲

1.确认参数和返回值

res用于存储最终答案,ans用于存储单一搜索答案

 public List<String> res = new ArrayList<>();

 public StringBuilder ans = new StringBuilder();

利用len表示当前res的长度,key表示当前遍历的数字

2.递归终止条件

如题可知当res的长度等于digits时递归终止

 if(len==digits.length()){//收集结果
            res.add(ans.toString());
            return;
        }
        

3.单一搜索过程

对digits进行遍历后对向下一个数字对应的字符进行递归

 
        String str=map.get(digits.charAt(key)-'0');//获取当前需要操作的字符串,如“23”,即先2,3
        
        for (int i =0; i <str.length() ; i++) {
              ans.append(str.charAt(i));//处理

              dfs(digits,map,len+1,key+1);//向下搜

              ans.delete(ans.length()-1,ans.length());//回溯

        }

完整代码:

class Solution {
    public HashMap<Integer, String> map = new HashMap<>();

    public List<String> res = new ArrayList<>();

    public StringBuilder ans = new StringBuilder();

    public void f(String digits) {
        map.put(2, "abc");
        map.put(3, "def");
        map.put(4, "ghi");
        map.put(5, "jkl");
        map.put(6, "mno");
        map.put(7, "pqrs");
        map.put(8, "tuv");
        map.put(9, "wxyz");
    }

     /**
     * 
     * @param digits  目标参数
     * @param map     映射map
     * @param num     当前操作的字符串在map中对应的key,
     * @param len     当前字符ans的长度
     */
    public  void dfs(String digits,HashMap<Integer,String> map,int len,int key ){
        f(digits);
        if(len==digits.length()){//收集结果
            res.add(ans.toString());
            return;
        }
        
        String str=map.get(digits.charAt(key)-'0');//获取当前需要操作的字符串,如“23”,即先2,3
        
        for (int i =0; i <str.length() ; i++) {
              ans.append(str.charAt(i));//处理

              dfs(digits,map,len+1,key+1);//向下搜

              ans.delete(ans.length()-1,ans.length());//回溯

        }
    }

    public List<String> letterCombinations(String digits) {
        if(digits==null||digits.length()==0){
            return res;
        }
        dfs(digits, map, 0,0);
        return res;
    }
}

复习题6

切割问题

131. 分割回文串 - 力扣(LeetCode)

 切割问题依然可以抽象成组合问题

递归用来纵向遍历,for循环用来横向遍历,切割线(就是图中的红线)切割到字符串的结尾位置,说明找到了一个切割方法。

判断回文

 public  boolean cheak(String s,int start,int end){
        for (int i = start,j=end; i <j ; i++,j--) {
            if(s.charAt(i)!=s.charAt(j)){
                return false;
            }
        }
        return true;
    }

 本题注意 利用startIndex代表分割线当startIndex大于了是s.length()也就到达了叶子节点、

完整代码:

class Solution {
       public List<List<String>>  res=new ArrayList<>();

    public  List<String> list=new ArrayList<>();

    public  boolean cheak(String s,int start,int end){
        for (int i = start,j=end; i <j ; i++,j--) {
            if(s.charAt(i)!=s.charAt(j)){
                return false;
            }
        }
        return true;
    }
    public  void dfs(String s,int startIndex) {
        if (startIndex >= s.length()) {
            res.add(new ArrayList(list));
            return;
        }
        for (int i = startIndex; i < s.length(); i++) {
            //如果是回文子串,则记录
            if (cheak(s, startIndex, i)) {
                String str = s.substring(startIndex, i + 1);
                list.add(str);
            } else {
                continue;
            }
            //起始位置后移,保证不重复
            dfs(s, i + 1);
            list.remove(list.size() - 1);
        }
    }
    public List<List<String>> partition(String s) {
        dfs(s, 0);
        return res;
    }
}

复习题7

子集问题

78. 子集 - 力扣(LeetCode)

子集问题与组合问题不同的是对所有的叶子节点都需要搜集,所以在搜集答案时不需要return 了

 

本题其实可以不需要加终止条件,因为startIndex >= nums.size(),本层for循环本来也结束了,本来我们就要遍历整棵树。

有的同学可能担心不写终止条件会不会无限递归?

并不会,因为每次递归的下一层就是从i+1开始的。

如果要写终止条件,注意:result.push_back(path);要放在终止条件的上面,如下:

result.push_back(path); // 收集子集,要放在终止添加的上面,否则会漏掉结果
if (startIndex >= nums.size()) { // 终止条件可以不加
    return;
}

 完整代码:

class Solution {
    public  List<List<Integer>> res=new ArrayList<>();

    public  List<Integer> list=new ArrayList<>();

    public  void dfs(int []nums,int startIndex){
        res.add(new ArrayList<>(list));
        for (int i = startIndex; i <nums.length ; i++) {
            list.add(nums[i]);
            dfs(nums,i+1);
            list.remove(list.size()-1);
        }
    }
    public List<List<Integer>> subsets(int[] nums) {
       dfs(nums,0);
       return res;
    }
}

在回溯算法:求子集问题(二) (opens new window)中,开始针对子集问题进行去重。

class Solution {
    public  List<List<Integer>> res=new ArrayList<>();

    public  List<Integer> list=new ArrayList<>();

    public  void dfs(int []nums,int startIndex){
        res.add(new ArrayList<>(list));
        for (int i = startIndex; i <nums.length ; i++) {
            if(i>startIndex&&nums[i]==nums[i-1]){
                continue;
            }
            list.add(nums[i]);
            dfs(nums,i+1);
            list.remove(list.size()-1);
        }
    }
    public List<List<Integer>> subsetsWithDup(int[] nums) {
          Arrays.sort(nums);
        dfs(nums,0);
        return res;
    }
}

复习题8

排列问题

46. 全排列 - 力扣(LeetCode)

排列问题与祝贺问题不同的是排列问题与与顺序有关如[1,2]和[2,1]是相同的组合但是是不同的排列

那么可能前面选了2后面可能选1 也就说明每次都从0开始,然后需要used数组对已经选过的数进行保存

完整代码:

class Solution {
    public List<List<Integer>> res = new ArrayList<>();
    public List<Integer> list = new ArrayList<>();
    public boolean[] isUse = new boolean[10];

    /**
     * @param nums 数组
     * @param x    当前放数的位置
     */
    public  void dfs(int[] nums, int x) {
        if (x == nums.length) {
            res.add(new ArrayList<>(list));
            return;
        }
        for (int i = 0; i < nums.length; i++) {
            if (!isUse[i]) {

                isUse[i] = true;
                list.add(nums[i]);

                dfs(nums, x + 1);

                isUse[i] = false;
                list.remove(list.size() - 1);
            }
        }
    }

    public List<List<Integer>> permute(int[] nums) {
dfs(nums, 0);
        return res;
    }
}

47. 全排列 II - 力扣(LeetCode)

在此题基础上增加去重过程即可

class Solution {
      public  List<List<Integer>> res = new ArrayList<>();
    public  List<Integer> list = new ArrayList<>();
    public  boolean[] isUse = new boolean[10];

    /**
     * @param nums 数组
     * @param x    当前放数的位置
     */
    public void dfs(int[] nums, int x) {
        if (x == nums.length) {
            res.add(new ArrayList<>(list));
            return;
        }
        HashSet<Integer> set = new HashSet<>();
        for (int i = 0; i < nums.length; i++) {
            if(set.contains(nums[i])){//对同层选过的节点进行去重
                continue;
            }
            if (!isUse[i]) {

                isUse[i] = true;
                list.add(nums[i]);
                set.add(nums[i]);

                dfs(nums, x + 1);

                isUse[i] = false;
                list.remove(list.size() - 1);
            }
        }
    }
    public List<List<Integer>> permuteUnique(int[] nums) {
        dfs(nums, 0);
        return res;
    }
}

关于去重

去重问题

以上我都是统一使用used数组来去重的,其实使用set也可以用来去重!

在本周小结!(回溯算法系列三)续集 (opens new window)中给出了子集、组合、排列问题使用set来去重的解法以及具体代码,并纠正一些同学的常见错误写法。

同时详细分析了 使用used数组去重 和 使用set去重 两种写法的性能差异:

使用set去重的版本相对于used数组的版本效率都要低很多,大家在leetcode上提交,能明显发现。

原因在回溯算法:递增子序列 (opens new window)中也分析过,主要是因为程序运行的时候对unordered_set 频繁的insert,unordered_set需要做哈希映射(也就是把key通过hash function映射为唯一的哈希值)相对费时间,而且insert的时候其底层的符号表也要做相应的扩充,也是费时的。

而使用used数组在时间复杂度上几乎没有额外负担!

使用set去重,不仅时间复杂度高了,空间复杂度也高了,在本周小结!(回溯算法系列三) (opens new window)中分析过,组合,子集,排列问题的空间复杂度都是O(n),但如果使用set去重,空间复杂度就变成了O(n^2),因为每一层递归都有一个set集合,系统栈空间是n,每一个空间都有set集合。

那有同学可能疑惑 用used数组也是占用O(n)的空间啊?

used数组可是全局变量,每层与每层之间公用一个used数组,所以空间复杂度是O(n + n),最终空间复杂度还是O(n)。

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

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

相关文章

算法003:快乐数

这道题采用快慢双指针的方法。 为了弄清楚这个题到底是要我们干嘛&#xff0c;我们把整个过程类比一下&#xff1a; 不管是n19还是n2&#xff0c;我们都把它当成一种判断链表是否有环的方式。 对于n19&#xff0c;题干是这样解释的&#xff1a; 我们把它当成链表&#xff0c…

麒麟v10系统arm64架构openssh9.7p1的rpm包

制作openssh 说明 理论上制作的多个rpm在arm64架构&#xff08;aarch64&#xff09;都适用 系统信息&#xff1a;4.19.90-17.ky10.aarch64 GNU/Linux 升级前备份好文件/etc/ssh、/etc/pam.d等以及开启telnet 升级后确认正常后关闭telnet 在之前制作过openssh-9.5p1基础上继续…

算法题--华为od机试考试(围棋的气、用连续自然数之和来表达整数、亲子游戏)

目录 围棋的气 题目描述 输入描述 示例1 输入 输出 解析 答案 用连续自然数之和来表达整数 题目描述 输入描述 输出描述 示例1 输入 输出 说明 示例2 输入 输出 解析 答案 亲子游戏 题目描述 输入描述 输出描述 示例1 输入 输出 说明 示例2 输入…

SpringBoot项目启动后访问网页显示“Please sign in“

SpringBoot启动类代码如下 SpringBoot项目启动后访问网页显示"Please sign in"&#xff0c;如图 这是一个安全拦截页面&#xff0c;即SpringSecurity认证授权页面&#xff0c;因为SecurityAutoConfiguration是Spring Boot提供的安全自动配置类&#xff0c;也就是说它…

使用onnxruntime加载YOLOv8生成的onnx文件进行目标检测

在网上下载了60多幅包含西瓜和冬瓜的图像组成melon数据集&#xff0c;使用 LabelMe 工具进行标注&#xff0c;然后使用 labelme2yolov8 脚本将json文件转换成YOLOv8支持的.txt文件&#xff0c;并自动生成YOLOv8支持的目录结构&#xff0c;包括melon.yaml文件&#xff0c;其内容…

React 18

创建 React 18 脚手架项目 全局安装 create-react-app npm install -g create-react-app yarn global add create-react-app . 确认是否已安装 create-react-app npm list -g create-react-app yarn global list | grep create-react-app . 如果安装失败 有时&#xff0…

Vue3整合Tailwindcss之padding样式类

04 常用基础样式 padding 样式类 什么是内边距 基础样式 ClassPropertiesp-0padding: 0px;px-0padding-left: 0px; padding-right: 0px;py-0padding-top: 0px; padding-bottom: 0px;ps-0padding-inline-start: 0px;pe-0padding-inline-end: 0px;pt-0padding-top: 0px;pr-0pa…

JVM 运行流程

JVM 是 Java 运行的基础&#xff0c;也是实现一次编译到处执行的关键&#xff0c;那么 JVM 是如何执行的呢&#xff1f; JVM 执行流程 程序在执行之前先要把java代码转换成字节码&#xff08;class 文件&#xff09;&#xff0c; JVM 首先需要把字节码通过一定的 方式 类加…

华为面经整理

文章目录 实习第一面准备提问相关算法相关 第一面结果提问环节 总结 实习 第一面准备 提问相关 操作系统有哪些功能 进程管理&#xff1a; 进程调度、进程同步和通信、多任务处理 内存管理&#xff1a; 内存分配、虚拟内存技术、内存保护 文件系统管理&#xff1a; 文件存储…

MMUNet:形态学特征增强网络在结肠癌病理图像分割中的应用

MMUNet: Morphological feature enhancement network for colon cancer segmentation in pathological images. 发表在&#xff1a;Biomedical Signal Processing and Control2024--影响因子&#xff1a;3.137 南华大学的论文 论文地址&#xff1a;main.pdf (sciencedirecta…

【ffmpeg】本地格式转换 mp4转wav||裁剪mp4

个人感受&#xff1a;太爽了&#xff01;&#xff01;&#xff01;&#xff08;可能用惯了转换网站和无良的转换软件&#xff09; ———— 使用FFmpeg把mp4文件转换为WAV文件 - 简书 (jianshu.com) FFMPEG 视频分割和合并 - 简书 (jianshu.com) ———— 示例 ffmpeg -i …

计算机组成结构—IO接口(IO控制器)

目录 一、I/O 接口的功能 二、I/O 接口的基本结构 1. 总线连接的数据通路 2. I/O 接口的基本组成 三、I/O 端口及其编址 1. 统一编址 2. 不统一编址 四、I/O 接口的类型 两个系统或两个部件之间的交接部分&#xff0c;一般就称为 接口。接口可以是硬件上两种设备间的连…

哈夫曼树的创建

要了解哈夫曼树&#xff0c;可以先了解一下哈夫曼编码&#xff0c;假设我们有几个字母&#xff0c;他们的出现频率是A: 1 B: 2 C: 3 D: 4 E: 5 F: 6 G: 7。那么如果想要压缩数据的同时让访问更加快捷&#xff0c;就要让频率高的字母离根节点比较进&#xff0c;容易访问&#xf…

微生物共生与致病性:动态变化与识别挑战

谷禾健康 细菌耐药性 抗生素耐药性细菌感染的发生率正在上升&#xff0c;而新抗生素的开发由于种种原因在制药行业受重视程度下降。 最新在《柳叶刀-微生物》&#xff08;The Lancet Microbe&#xff09;上&#xff0c;科学家提出了基于细菌适应性、竞争和传播的生态原则的跨学…

个人vsCode配置文件<setting.js>

个人vsCode配置文件setting.js 快速打开1、使用快捷键 CtrlShiftP &#xff0c;然后搜索setting2、手动 自用配置 快速打开 1、使用快捷键 CtrlShiftP &#xff0c;然后搜索setting 2、手动 自用配置 {"terminal.integrated.profiles.windows": {"PowerShell&…

阿里云对象存储OSS简单使用

文章目录 概念基本概念Bucket 准备工作控制台操作对象存储OSSJava客户端操作对象存储OSS参考来源 概念 基本概念 阿里云对象存储 OSS是一款海量、安全、低成本、高可靠的云存储服务&#xff0c;提供最高可达 99.995 % 的服务可用性。而且提供了多种存储类型&#xff0c;降低我…

顶顶通呼叫中心中间件-asr录音路径修改(mod_cti基于FreeSWITCH)

顶顶通呼叫中心中间件-asr录音路径修改(mod_cti基于FreeSWITCH) 录音路径模板。如果不是绝对路径&#xff0c;会把这个路径追加到FreeSWITCH的recordings后面。支持变量&#xff0c;比如日期 ${strftime(%Y-%m-%d)}。最后一个录音文件路径会保存到变量 ${cti_asr_last_record_…

PDF 文件的解析

1、文本 PDF 的解析 1.1、文本的提取 进行文本提取的 Python 库包括&#xff1a;pdfminer.six、PyMuPDF、PyPDF2 和 pdfplumber&#xff0c;效果最好的是 PyMuPDF&#xff0c;PyMuPDF 在进行文本提取时能够最大限度地保留 PDF 的阅读顺序&#xff0c;这对于双栏 PDF 文件的抽…

刷完50题,搞定十大网络基础知识

号主&#xff1a;老杨丨11年资深网络工程师&#xff0c;更多网工提升干货&#xff0c;请关注公众号&#xff1a;网络工程师俱乐部 上午好&#xff0c;我的网工朋友 咱新手网工&#xff0c;入行之前最需要做的准备之一&#xff0c;就是抓住网络基础知识&#xff0c;毕竟是饭碗&…

C语言野指针、规避野指针、assert宏断言

目录 a.野指针成因 1.指针未初始化 2.指针越界访问 3.指针指向的空间释放 b.规避野指针 1.指针初始化 2.小心指针越界 3.指针变量不再使用时&#xff0c;及时置NULL&#xff0c;指针使用之前检查有效性 4.避免返回局部变量的地址 c.assert宏断言的使用 概念&#xff1…