《剑指 Offer》专项突破版 - 面试题 79 ~ 84 : 详解回溯法(C++ 实现)

目录

一、回溯法的基础知识

二、集合的排列、组合

面试题 79 : 所有子集

面试题 80 : 包含 k 个元素的组合

面试题 81 : 允许重复选择元素的组合

面试题 82 : 包含重复元素集合的组合

面试题 83 : 没有重复元素集合的全排列

面试题 84 : 包含重复元素集合的全排列


 


一、回溯法的基础知识

回溯法可以看作蛮力法的升级版本,它在解决问题时的每一步都尝试所有可能的选项,最终找出所有可行的解决方案。回溯法非常适合解决由多个步骤组成的问题,并且每个步骤都有多个选项。在某一步选择了其中一个选项之后,就进入下一步,然后会面临新的选项。就这样重复选择,直至到达最终的状态

用回溯法解决问题的过程可以形象地用一个树形结构表示,求解问题的每个步骤可以看作树中的一个节点。如果在某一步有 n 个可能的选项,每个选项是树中的一条边,经过这些边就可以到达该节点的 n 个子节点

例如,可以用回溯法求集合 [1, 2] 的所有子集,求解的过程可以用下图中的树形结构表示。树的根节点对应子集的初始状态,即子集是空的。求集合 [1, 2] 的子集分为两步,第一步决定是否在子集中添加数字 1,此时面临两个选择,添加 1 或不添加 1。如果选择不添加 1,那么子集仍然保持空集,前往第 2 层第 1 个节点。此时第二步再次面临两个选择,添加 2 或不添加 2。如果此时选择不添加 2,那么子集仍然是空的,如下图中第 3 层的第 1 个节点所示;如果此时选择添加 2,那么子集中有一个元素 2,如下图中第 3 层的第 2 个节点所示。

如果在根节点选择添加 1,那么在子集中添加元素 1,子集变成 [1],前往下图中第 2 层的第 2 个节点。此时第二步再次面临两个选择,添加 2 或不添加 2。如果此时选择不添加 2,那么最终子集中仍然只有元素 1,对应下图中第 3 层的第 3 个节点;如果此时选择添加 2,那么子集变成 [1, 2],对应下图中的第 3 层的第 4 个节点。

在采用回溯法解决问题时如果到达树形结构的叶节点,就找到了问题的一个解。如果希望找到更多的解,那么还可以回溯到它的父节点再尝试父节点其他的选项。如果父节点所有可能的选项都已经试过,那么再回溯到父节点的父节点以尝试它的其他选项,这样逐层回溯到树的根节点。因此,采用回溯法解决问题的过程实质上是在树形结构中从根节点开始进行深度优先遍历。通常,回溯法的深度优先遍历用递归代码实现。

如果在前往某个节点时对问题的解的状态进行了修改,那么在回溯到它的父节点时记得清除相应的修改。例如,在求集合 [1, 2] 的所有子集的过程中,当位于上图中树形结构第 2 层的第 1 个节点时,选择添加 2 前往第 3 层的第 2 个节点,此时在子集(这个问题的解)中添加元素 2。此时已经到达了树的叶节点。为了找出更多的子集(问题的其他解),可以回溯到它的父节点,即第 2 层的第 1 个节点。回溯到父节点时需要清除之前的修改,从子集中删除刚刚添加的元素 2,于是子集重新变成一个空集。由于此时已经尝试了第 2 层的第 1 个节点的所有选项,因此再次回溯到它的父节点,即根节点,再选择添加 1 前往第 2 层的第 2 个节点,在一个空集中添加元素 1,此时子集为 [1]。

由于回溯法是在所有选项形成的树上进行深度优先遍历,如果解决问题的步骤较多或每个步骤都面临多个选项,那么遍历整棵树将需要较多的时间。如果明确知道某些子树没有必要遍历,那么在遍历的时候应该避开这些子树以优化效率。通常将使用回溯法时避免遍历不必要的子树的方法称为剪枝


二、集合的排列、组合

  1. 从 n 个不同元素中,任取 m 个元素(0 <= m <= n)并按照某种顺序形成一个排列。m 等于 n 的排列又称为全排列。从 n 个不同元素中取出 m个元素的所有排列的个数叫做排列数,用符合 A(n, m) 或 A_n^m 表示,A_n^m = n(n-1)(n-2)···(n-m+1) = \dfrac{n!}{(n-m)!}

    从 n 个不同元素中取出 m 个元素排成一列,第 1 个位置有 n 个选择,第 2 个位置有 n - 1 个选择(因为已经有 1 个元素放在前一个位置),第 3 个位置有 n - 2 个选择,以此类推第 m 个位置有 n - (m - 1) 个选择,所以 A_n^m = n(n-1)(n-2)···(n-m+1) = \dfrac{n!}{(n-m)!}

    规定:0! = 1。

  2. 从 n 个不同元素中,任取 m 个元素形成一个组合。从 n 个不同元素中取出 m 个元素的所有组合的个数叫做组合数,用符合 C(n, m) 或 C_n^m 表示,C_n^m = \dfrac{A_n^m}{m!} = \dfrac{n!}{m!(n-m)!}

    计算组合数的公式是由计算排列数的公式去掉重复的部分推到而来的。从 n 个不同元素中取出 m 个元素组成一组,由于 m 个元素组成的一组有 m! 种不同的排列,所以 。

    注意:。

排列、组合是数学中很重要的两个概念,在编程面试中也经常遇到。接下来详细讨论如何采用回溯法求出集合中的子集(组合)和排列

面试题 79 : 所有子集

题目

输入一个不含重复数字的数据集合,请找出它的所有子集。例如,数据集合 [1, 2] 有 4 个子集,分别是 []、[1]、[2] 和 [1, 2]。

分析

所谓子集就是从一个集合中选出若干元素。如果集合中包含 n 个元素,那么生成子集可以分为 n 步,每一步从集合中取出一个数字,此时面临两个选择,将该数字添加到子集中或不将该数字添加到子集中。生成一个子集可以分为若干步,并且每一步都面临若干选择,这正是应用回溯法的典型场景。

代码实现

class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        vector<vector<int>> result;
        vector<int> subset;
        dfs(nums, 0, result, subset);
        return result;
    }
private:
    void dfs(vector<int>& nums, int index, vector<vector<int>>& result, vector<int>& subset) {
        if (index == nums.size())
        {
            result.push_back(subset);
            return;
        }
​
        dfs(nums, index + 1, result, subset);  // 不将 nums[index] 添加到子集中
​
        subset.push_back(nums[index]);  // 将 nums[index] 添加到子集中
        dfs(nums, index + 1, result, subset);
        subset.pop_back();
    }
};

面试题 80 : 包含 k 个元素的组合

题目

输入 n 和 k,请输出从 1 到 n 中选取 k 个数字组成的所有组合。例如,如果 n 等于 3,k 等于 2,将组成 3 个组合,分别是 [1, 2]、[1, 3] 和 [2, 3]。

分析

集合的一个组合也是一个子集,因此求集合的组合的过程和求子集的过程是一样的。这个题目只是增加了一个限制条件,即只找出包含 k 个数字的组合。只需要在面试题 79 的代码的基础上稍加修改,就可以找出所有包含 k 个数字的组合。

代码实现

class Solution {
public:
    vector<vector<int>> combine(int n, int k) {
        vector<vector<int>> result;
        vector<int> combination;
        dfs(n, k, 1, result, combination);
        return result;
    }
private:
    void dfs(int n, int k, int num, vector<vector<int>>& result, vector<int>& combination) {
        if (combination.size() == k)
        {
            result.push_back(combination);
            return;
        }
​
        if (num == n + 1)
            return;
​
        dfs(n, k, num + 1, result, combination);
​
        combination.push_back(num);
        dfs(n, k, num + 1, result, combination);
        combination.pop_back();
    }
};

面试题 81 : 允许重复选择元素的组合

题目

给定一个没有重复数字的正整数集合,请找出所有元素之和等于某个给定值的所有组合。同一个数字可以在组合中出现任意次。例如,输入正整数集合 [2, 3, 5],元素之和等于 8 的组合有 3 个,分别是 [2, 2, 2, 2]、[2, 3, 3] 和 [3, 5]。

分析

这个题目仍然是关于组合的,但组合中的一个数字可以出现任意次。可以以不变应万变,用回溯法来解决这个问题。

能够用回溯法解决的问题都能够分成若干步来解决,每一步都面临若干选择。对于从集合中选取数字组成组合的问题而言,集合中有多少个数字,解决这个问题就需要多少步。每一步都从集合中取出一个下标为 index 的数字,此时面临两个选择。一个选择是跳过这个数字不将该数字添加到组合中,那么这一步实际上什么都不做,接下来处理下标为 index + 1 的数字。另一个选择是将数字添加到组合中,由于一个数字可以重复在组合中出现,也就是说,下一步可能再次选择同一个数字,因此下一步仍然处理下标为 index 的数字

代码实现

class Solution {
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        vector<vector<int>> result;
        vector<int> combination;
        dfs(candidates, target, 0, result, combination);
        return result;
    }
private:
    void dfs(vector<int>& candidates, int target, int index, 
        vector<vector<int>>& result, vector<int>& combination) {
        if (target == 0)
        {
            result.push_back(combination);
            return;
        }
        
        if (target < 0 || index == candidates.size())
            return;
        
        dfs(candidates, target, index + 1, result, combination);
​
        combination.push_back(candidates[index]);
        dfs(candidates, target - candidates[index], index, result, combination);
        combination.pop_back();
    }
};

应用回溯法解决问题时如果有可能应尽可能剪枝以优化时间效率。由于题目明确指出数组中的所有数字都是正整数,因此当组合中已有数字之和已经大于目标值时(即递归函数 dfs 的参数 target 的值小于 0 时)就没有必要再考虑数组中还没有处理的数字,因为再在组合中添加任意正整数元素之后和会更大,一定找不到新的符合条件的组合,也就没必要再继续尝试

面试题 82 : 包含重复元素集合的组合

题目

给定一个可能包含重复数字的整数集合,请找出所有元素之和等于某个给定值的所有组合。注意:整数集合中的每个数字在每个组合中只能使用一次,输出中不得包含重复的组合。例如,输入整数集合 [2, 2, 2, 4, 3, 3],元素之和等于 8 的组合有 2 个,分别是 [2, 2, 4] 和 [2, 3, 3]。

分析

这个题目和之前几个与组合相关的题目相比,最大的不同在于输入的集合中有重复的数字但输出不得包含重复的组合。如果输入的集合中有重复的数字,不经过特殊处理将产生重复的组合。例如,从集合 [2, 2, 2] 中选出 2 个数字本来能组成 3 个组合,但 3 个组合都是 [2, 2],因此它们是重复的组合,在本题中只能算 1 个。另外,组合不考虑数字的顺序,如组合 [2, 2, 4]、[2, 4, 2] 和 [4, 2, 2] 只能算一个组合

避免重复的组合的方法是当在某一步决定跳过某个值为 m 的数字时,跳过所有值为 m 的数字。例如,假设求 [2, 2, 2] 的组合,如果在处理第 1 个 2 时跳过它并跳过所有的 2,那么得到的是一个空的组合。如果在选择第 1 个 2 之后决定跳过第 2 个 2 并连带跳过后面的 2,那么得到的是组合 [2]。如果选择前两个 2 之后决定跳过第 3 个 2,那么得到的是组合 [2, 2]。如果 3 个 2 都被选择,则得到组合 [2, 2, 2]。采用这种方法就可以避免产生重复的组合,如避免了两种产生重复组合 [2, 2] 的情形,一种情形是跳过第 1 个 2 选择后面两个 2,另一种情形是跳过中间一个 2 选择前后两个 2。

采用这种方法可以避免产生重复的组合,如果组合中只有一个 2,那么选择的一定是第 1 个 2,而不是第 2 个或第 3 个 2;如果组合中有两个 2,那么选择的一定是第 1 个和第 2 个 2,而不是第 1 个和第 3 个,或第 2 个和第 3 个

为了方便跳过后面所有值相同的数字,可以将集合中的所有数字排序,把相同的数字放在一起,这样方便比较数字。当决定跳过某个值的数字时,可以按顺序扫描后面的数字,直到找到不同的值为止

代码实现

class Solution {
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end());
​
        vector<vector<int>> result;
        vector<int> combination;
        dfs(candidates, target, 0, result, combination);
        return result;
    }
private:
    void dfs(vector<int>& candidates, int target, int index, 
        vector<vector<int>>& result, vector<int>& combination) {
        if (target == 0)
        {
            result.push_back(combination);
            return;
        }
​
        if (target < 0 || index == candidates.size())
            return;
​
        int nextIndex = index + 1;
        while (nextIndex < candidates.size() && candidates[nextIndex] == candidates[index])
        {
            ++nextIndex;
        }
        dfs(candidates, target, nextIndex, result, combination);
​
        combination.push_back(candidates[index]);
        dfs(candidates, target - candidates[index], index + 1, result, combination);
        combination.pop_back();
    }
};

面试题 83 : 没有重复元素集合的全排列

题目

给定一个没有重复数字的集合,请找出它的所有全排列。例如,集合 [1, 2, 3] 有 6 个全排列,分别是 [1, 2, 3]、[1, 3, 2]、[2, 1, 3]、[2, 3, 1]、[3, 1, 2] 和 [3, 2, 1]。

分析

排列和组合不同,排列与元素的顺序相关,交换数字能够得到不同的排列。生成全排列的过程,就是交换输入集合中元素的顺序以得到不同的排列

下面以输入集合 [1, 2, 3] 为例分析生成全排列的过程。先考虑排列的第 1 个数字。数字 1 可以是排列的第 1 个数字;同样,数字 2、3 也可以是排列的第 1 个数字。因此,当生成排列的第 1 个数字时会面临 3 个选项,即可以分别选择 1、2 或 3。选择某个数字作为排列的第 1 个数字之后接下来生成排列的第 2 个数字。假设选择数字 3 成为排列的第 1 个数字,那么生成第 2 个数字时就面临两个选项,即数字 1 或 2 都有可能成为排列的第 2 个数字。接下来生成排列的第 3 个数字。由于已经选择了两个数字作为排列的前两个数字,因此到第 3 个数字时只剩下 1 个数字,此时也就只有 1 个选项。

如果输入的集合中有 n 个元素,那么生成一个全排列需要 n 步。当生成排列的第 1 个数字时会面临 n 个选项,即 n 个数字都有可能成为排列的第 1 个数字。生成排列的第 1 个数字之后接下来生成第 2 个数字,此时面临 n - 1 个选项,即剩下的 n - 1 个数字都有可能成为第 2 个数字。然后以此类推,直到生成最后一个数字,此时只剩下 1 个数字,也就只有 1 个选项。看起来解决这个问题可以分成 n 步,而且每一步都面临若干选项,这是典型的适用回溯法的场景。

代码实现

class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int>> result;
        dfs(nums, 0, result);
        return result;
    }
private:
    void dfs(vector<int>& nums, int index, vector<vector<int>>& result) {
        if (index == nums.size())
        {
            result.push_back(nums);
            return;
        }

        for (int i = index; i < nums.size(); ++i)
        {
            if (index != i)
                swap(nums[index], nums[i]);  // 生成全排列的下标为 index 的数字
            
            dfs(nums, index + 1, result);

            if (index != i)
                swap(nums[index], nums[i]);
        }
    }
};

递归函数 dfs 生成输入数组 nums 的所有全排列,在函数执行过程中数组 nums 保存着当前排列的状态

当函数 dfs 生成排列的下标为 index 的数字时,排列的下标从 0 到 index - 1 的数字都已经选定,但数组 nums 中下标从 index 到 n - 1 的数字(假设数组的长度为 n)都有可能放到排列的下标为 index 的位置,因此函数 dfs 中有一个 for 循环逐一用下标 index 的数字交换它后面的数字。这个 for 循环包含下标为 index 的数字本身,这是因为它自己也能放到下标为 index 的位置。交换之后接着调用递归函数生成排列中下标为 index + 1 的数字。由于之前已经交换了数组中的两个数字,修改了排列的状态,在结束一次for 循环之前需要清除对排列状态的修改,因此再次交换之前交换的两个数字

当下标 index 等于数组 nums 的长度时,排列的每个数字都已经产生了,nums 中保存了一个完整的全排列,于是将该全排列添加到 result 中。最终 result 中包含所有的全排列。

假设数组 nums 的长度为 n,当 index 等于 0 时递归函数 dfs 中的 for 循环执行 n 次,当 index 等于 1 时 for 循环执行 n - 1 次,以此类推,当 index 等于 n - 1 时,for 循环执行 1 次。因此,全排列的时间复杂度是 O(n!)

面试题 84 : 包含重复元素集合的全排列

题目

给定一个包含重复数字的集合,请找出它的所有全排列。例如,集合 [1, 1, 2] 有 3 个全排列,分别是 [1, 1, 2]、[1, 2, 1] 和 [2, 1, 1]。

分析

下面的代码和面试题 83 的代码大同小异。不同点在于下面代码的递归函数 dfs 中使用了一个 unordered_set,用来保存已经交换到全排列的下标为 index 的所有数字。只有当一个数字之前没有被交换到下标为 index 的位置时才做交换,否则直接跳过

代码实现

class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<vector<int>> result;
        dfs(nums, 0, result);
        return result;
    }
private:
    void dfs(vector<int>& nums, int index, vector<vector<int>>& result) {
        if (index == nums.size())
        {
            result.push_back(nums);
            return;
        }

        unordered_set<int> us;
        for (int i = index; i < nums.size(); ++i)
        {
            if (us.count(nums[i]) == 0)
            {
                us.insert(nums[i]);
                if (index != i)
                    swap(nums[index], nums[i]);
                
                dfs(nums, index + 1, result);
            
                if (index != i)
                    swap(nums[index], nums[i]);
            }
        }
    }
};

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

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

相关文章

wsl中安装虚拟环境virtualenv,pycharm中配置wsl解释器

wsl 中安装虚拟环境 virtualenv 注意&#xff1a; 不能将虚拟环境安装到 /root 目录下&#xff0c;在 window 文件管理中&#xff0c;没有权限访问 wsl 中的 /root 目录 安装虚拟环境 sudo pip install virtualenv sudo pip install virtualenvwrapper配置环境变量 1、创建…

基于单片机的指纹打卡机设计

摘要 在科学技术飞速发展的今天&#xff0c;社会对身份识别的要求越来越高&#xff0c;尤其是在企业管理的人员签到、工作考勤等活动中对身份识别的高效性和可靠性的需求更为迫切。而传统的个人身份识别手段&#xff0c;如钥匙、密码、IC卡等&#xff0c;由于具有可盗用、可伪…

深入理解TCP:序列号、确认号和自动ACK的艺术

深入理解TCP&#xff1a;序列号、确认号和自动ACK的艺术 在计算机网络的世界里&#xff0c;TCP&#xff08;传输控制协议&#xff09;扮演着至关重要的角色。它确保了数据在不可靠的网络环境中可靠地、按顺序地传输。TCP的设计充满智慧&#xff0c;其中序列号&#xff08;Seq&a…

续上篇 qiankun 微前端配置

上篇文章地址&#xff1a;微前端框架 qiankun 配置使用【基于 vue/react脚手架创建项目 】-CSDN博客 主应用&#xff1a; src/main.js 配置&#xff1a; import Vue from vue import App from ./App.vue import router from ./router import { registerMicroApps, start } …

Jenkins内部使用Docker

修改docker.sock文件权限 路径在&#xff1a;/var/run/docker.sock 进入/var/run目录下 修改docker.sock文件权限&#xff0c;且让其他用户也可以读写。 cd /var/run chown root:root docker.sock chmod orw docker.sock 修改数据卷映射 切换到你Jenkins的docker-compose.…

Verdaccio部署及基础使用

1. Verdaccio 简介 Verdaccio&#xff0c;是一个轻量级的 npm 私有仓库的开源解决方案。npm是一个基于http的协议&#xff0c;用来存放软件包并且维护版本和依赖&#xff0c;利用 http 提供的 url路径 来对软件包进行增删改查。所以 Verdaccio 这款软件的核心就是实现 npm协议…

淘宝商品销量数据接口,淘宝API接口

淘宝商品销量数据接口是淘宝开放平台提供的一种API接口&#xff0c;通过该接口&#xff0c;商家可以获取到淘宝平台上的商品销量数据。 淘宝商品销量数据接口可以用于获取特定商品的销量数据、特定店铺的销量数据、特定类目的销量数据等。商家可以根据自己的需求来调用该接口&…

Gitlab部署及使用

1. 简介 GitLab 是一个用于仓库管理系统的开源项目&#xff0c;使用 Git 作为代码管理工具&#xff0c;并在此基础上搭建起来的Web服务。Gitlab是目前被广泛使用的基于 git 的开源代码管理平台&#xff0c;基于Ruby on Rails构建&#xff0c;主要针对软件开发过程中产生的代码…

phpcms头像上传漏洞引发的故事

目录 关键代码 第一次防御 第一次绕过 第二次防御 第二次绕过 第三次防御 第三次绕过 如何构造一个出错的压缩包 第四次防御 第四次绕过 本篇文章是参考某位大佬与开发人员对于文件包含漏洞的较量记录下的故事&#xff0c;因为要学习文件包含漏洞&#xff0c;就将大佬…

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:GridItem)

网格容器中单项内容容器。 说明&#xff1a; 该组件从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。仅支持作为Grid组件的子组件使用。 子组件 可以包含单个子组件。 接口 GridItem GridItem(value?: GridItemOptions)…

【AI+办公】利用AI软件制作PPT提升工作效率

最近看了很多AI相关信息的输入&#xff0c;很多使用AI软件赚钱的文章或付费课程&#xff0c;思路多多少少自己了解不少&#xff0c;后面有时间分享下。本篇主题是&#xff0c;利用AI软件制作PPT提升工作效率。对于上班族来说&#xff0c;提升工作效率也是一种节省个人时间的方式…

最佳实践:Swagger 自动生成 Api 文档

自动生成 API 文档的好处不言而喻&#xff0c;它可以提供给你的团队或者外部协作者&#xff0c;方便 API 使用者准确地调用到你的 API。为了降低手动编写文档带来的错误&#xff0c;很多 API 开发者会偏向于寻找一些好的方法来自动生成 API 文档。本文将会介绍一些常用的文档生…

express+mysql+vue,从零搭建一个商城管理系统14--快递查询(对接快递鸟)

提示&#xff1a;学习express&#xff0c;搭建管理系统 文章目录 前言一、安装md5&#xff0c;axios&#xff0c;qs二、新建config/logistics.js三、修改routes/order.js四、添加商品到购物车总结 前言 需求&#xff1a;主要学习express&#xff0c;所以先写service部分 快递鸟…

AI人工智能培训讲师ChatGPT讲师叶梓培训简历及提纲ChatGPT等AI技术在医疗领域的应用

叶梓&#xff0c;上海交通大学计算机专业博士毕业&#xff0c;高级工程师。主研方向&#xff1a;数据挖掘、机器学习、人工智能。历任国内知名上市IT企业的AI技术总监、资深技术专家&#xff0c;市级行业大数据平台技术负责人。 长期负责城市信息化智能平台的建设工作&#xff…

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:ListItemGroup)

该组件用来展示列表item分组&#xff0c;宽度默认充满List组件&#xff0c;必须配合List组件来使用。 说明&#xff1a; 该组件从API Version 9开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。该组件的父组件只能是List。 使用说明 当List…

云与云计算:从传统到云端的IT资源变革

云&#xff1a;从分散到集约&#xff0c;资源服务化的新模式 让我们先通过一个生活化的场景来理解“云”这一概念。几十年前&#xff0c;诸如农村地区的居民需要自给自足&#xff0c;比如在自家院子里打井取水&#xff0c;冬季烧煤取暖&#xff0c;一切满足自己生活需要的都要…

SwiftUI组件 - AsyncImage

SwiftUI组件-AsyncImage import SwiftUIstruct AsyncImageBootcamp: View {let url URL(string: "https://picsum.photos/200")var body: some View {/// Mark - iOS15 以后才有的方法ScrollView {AsyncImage(url: url, content: { returnImage inreturnImage.resiz…

STM32F103 CubeMX 使用USB生成鼠标设备

STM32F103 CubeMX 使用USB生成鼠标设备 1 配置cubeMX1.1配置外部晶振&#xff0c;配置debug口1.2 配置USB1.3 配置芯片的时钟1.4 生成工程 2. 编写代码2.1 添加申明2.2 main函数代码 1 配置cubeMX 1.1配置外部晶振&#xff0c;配置debug口 1.2 配置USB 1.3 配置芯片的时钟 需…

uniapp 开发出现这个 Error in onLoad hook: “SyntaxError: Unexpected end of JSON input“

uniapp 开发出现这个 Error in onLoad hook: “SyntaxError: Unexpected end of JSON input“,这个问题如何解决。 原因&#xff1a; 由于JSON.parse无法识别某些url中的特殊字符比如&等特殊符号造成的。 解决办法&#xff1a; 页面A&#xff08;JSON.stringify传参&a…

YOLOV5 部署:基于web网页的目标检测(本地、云端均可)

1、前言 YOLOV5推理的代码很复杂,大多数都是要通过命令行传入参数进行推理,不仅麻烦而且小白不便使用。 本章介绍的web推理,仅仅需要十几行代码就能实现本地推理,并且只需要更改单个参数就可以很方便的部署云端,外网也可以随时的使用 之前文章介绍了QT的可视化推理界面,…