算法基础-----【递归回溯】

1、递归

递归是一种算法结构,递归会出现在子程序中自己调用自己或间接地自己调用自己。递归就是分为递去和归来。

递去:递归的问题必须可以分解为若干规模较小,与原问题相同的子问题,这些子问题可以用相同的解题思路解决。

归来:这些问题的演化过程是一个从小到大、由远及近的过程,并且会有一个明确的终点,一旦到了这个明确的终点后,就需要从原路返回到原点了(类比迷宫的分叉点),原问题就能解决了。

数学归纳法三个关键要素:

1)步进表达式:问题蜕变成子问题的表达式
2)结束条件:什么时候可以不再使用步进表达式
3)直接求解表达式:在结束条件下能够直接计算返回值的表达式

模板一:在递去中解决问题(回溯法模板)

  function recursion(大规模){if(end_condition){		//找到一个可行解,返回end;			//给出到达递归边界需要进行的处理}else{				//在将问题转换为子问题的每一步,解决该步中剩余部分的问题solve;			//解决该步中的剩余问题,递去recursion(小规模);	//转换为下一个子问题,递到最深处不断归来}
}

模板二:在归来的过程中解决问题(分治法模板)

  function recursion(大规模){if(end_condition){		//找到一个可行解,返回end;			//给出到达递归边界需要进行的处理}else{				//在将问题转换为子问题的每一步,解决该步中剩余部分的问题recursion();		//递去slove;	//递到最深处,不断归来}
}

2、回溯算法(DFS暴力)

回溯是一种算法思想,可以用递归实现。回溯是递归的副产品,只要有递归就会有回溯。(回溯函数=递归函数)。回溯的本质就是穷举,穷举所有可能,然后选择我们想要的答案。果想让回溯法高效一些,可以加一些剪枝的操作,但也改不了回溯法就是穷举的本质。回溯解决的问题都可以抽象为树形结构,因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度,都构成的树的深度。递归就要有终止条件,所以必然是一棵高度有限的树(N叉树)。

for循环横向遍历,递归纵向遍历,回溯不断调整结果集

2.1回溯法解决的问题:

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

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

组合是不强调元素顺序的,排列是强调元素顺序。{1, 2} 和 {2, 1} 在组合上,就是一个集合,因为不强调顺序。而要是排列的话,{1, 2} 和 {2, 1} 就是两个集合了。

2.2回溯三部曲

递归三部曲(树)

回溯三部曲:

1.回溯函数模板返回值以及参数(void backtracking(参数))

回溯算法需要的参数可不像二叉树递归的时候那么容易一次性确定下来,所以一般是先写逻辑,然后需要什么参数,就填什么参数。

2.回溯函数终止条件

​ 一般来说搜到叶子节点了,也就找到了满足条件的一条答案,把这个答案存放起来,并结束本层递归。

if (终止条件) {存放结果;return;
}

3.回溯搜索的遍历过程

回溯法一般是在集合中递归搜索,集合的大小构成了树的宽度,递归的深度构成的树的深度。

在这里插入图片描述

void backtracking(参数)if(终止条件){存放结果;return;}for(选择:本层集合中的元素(树中节点孩子的数量就是集合大小)){处理节点;backtracking(路径,选择列表);回溯,撤消处理结果}

回溯算法题解

1、组合问题

【77】组合问题

class Solution {
private:vector<vector<int>> res;//存放最终的结果vector<int> path;//存放一次递归的结果//n,k,每次开始的indexvoid backtracking(int n,int k,int startindex){//1.回溯终止条件if(path.size() == k){res.push_back(path);return;}//2.本层元素 单层递归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) {backtracking(n,k,1);return res;}
};

【216】组合总和Ⅲ

class Solution {
public:vector<vector<int>> combinationSum3(int k, int n) {backtracking(k,n,1);return res;}
private:void backtracking(int k,int n,int startindex){if(sum > n)return;//减枝if(path.size() == k){if(sum == n) res.push_back(path);return;}for(int i = startindex;i<=9;i++){sum+=i;path.push_back(i);backtracking(k, n,i+1);sum-=i;path.pop_back();}}int sum =0;vector<vector<int>> res;vector<int> path;
};

【17】电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
class Solution {public:vector<string> letterCombinations(string digits) {//边界判定if(digits.size() == 0)return res;backtracking(digits,0);return res;}
private:void backtracking(string digits,int index){if(index == digits.size()){res.push_back(path);return;}//索引数组int digit  = digits[index] -'0';//转为int//当层遍历string letter = mp[digit];for(int i =0;i<letter.size();i++){auto iter = mp.find(digit);path.push_back(iter->second[i]);backtracking(digits,i+1);//index+1path.pop_back();}}unordered_map<int,string> mp = {{0,""},{1,""},{2,"abc"},{3,"def"},{4,"ghi"},{5,"jkl"},{6,"mno"},{7,"pqrs"},{8,"tuv"},{9,"wxyz"}};vector<string> res;string path;
};

【39】组合总和

输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。

class Solution {
public:vector<vector<int>> combinationSum(vector<int>& candidates, int target) {if(candidates.size() == 0)return res;path.clear();res.clear();backtracking(candidates,target,0);return res;}private:void backtracking(vector<int>& candidates, int target,int index){if(sum == target){res.push_back(path);//sum不需要等于0,直接吐出来回溯其他的return;}if(sum>target||res.size()>150){//递归结束return;}for(int i =index;i<candidates.size();i++){//本层sum+=candidates[i];path.push_back(candidates[i]);backtracking(candidates,target,i);//回溯 注意传入i,可以重复传入sum-=candidates[i];path.pop_back();//这里没对}}int sum =0;vector<int> path;//存放路径vector<vector<int>> res;//存放path
};

【40】组合总和II

给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用 一次

**注意:**解集不能包含重复的组合。
输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]

集合(数组candidates)有重复元素,但还不能有重复的组合。去重:不同组合不能有重复的元素,也就是说去重的是同一层树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重。
强调一下,树层去重的话,需要对数组排序!
在这里插入图片描述
在这里插入图片描述

先给数组排序,然后要加if条件排除同层相同的元素

class Solution {
public:vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {path.clear();res.clear();sort(candidates.begin(),candidates.end());//排序backtracking(candidates,target,0);return res;}
private:void backtracking(vector<int>& nums,int target,int startindex){if(sum>target){return;}if(sum ==target ){res.push_back(path);}for(int i = startindex;i<nums.size();i++){if(i > startindex &&nums[i-1] == nums[i]){//从第二次开始,不能有重复的 写反的逻辑continue;}else{sum+=nums[i];path.push_back(nums[i]);backtracking(nums,target,i+1);//每个数只能用一次sum-=nums[i];path.pop_back();}}}vector<vector<int>> res;vector<int> path;int sum;
};

切割问题

【131】分割回文串

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串。返回 s 所有可能的分割方案。

class Solution {
public:vector<vector<string>> partition(string s) {backtracking(s,0);return res;}
private:void backtracking(string s,int startindex){if(startindex >= s.size()){//终止条件res.push_back(path);return;}//单层回溯for(int i =startindex;i<s.size();i++){if(isParo(s,startindex, i)){//判断是否是回文数组string str = s.substr(startindex, i - startindex + 1);path.push_back(str);}else{continue;}backtracking(s,i+1);path.pop_back();}}//判断是否是回文串 变式,加入了start和endbool isParo(const string& s,int start,int end){for(int i =start,j=end;i<j;i++,j--){if(s[i]!=s[j]){return false;}}return true;}vector<vector<string>> res;vector<string> path;};

【 93】复原IP地址

有效 IP 地址 正好由四个整数(每个整数位于 0255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔。

  • 例如:"0.1.2.201" "192.168.1.1"有效 IP 地址,但是 "0.011.255.245""192.168.1.312""192.168@1.1"无效 IP 地址。

给定一个只包含数字的字符串 s ,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s 中插入 '.' 来形成。你 不能 重新排序或删除 s 中的任何数字。你可以按 任何 顺序返回答案。

class Solution {
public:vector<string> restoreIpAddresses(string s) {res.clear();if(s.size()>12||s.size()<4)return res;backtracking(s,0);return res;}
private:void backtracking(string &s,int startindex){//到终点if(pointcount == 3){if(isValid(s,startindex,s.size()-1)){//判断第四段的数字是不是合法的res.push_back(s); }return;}//本层遍历int i;for( i =startindex;i< s.size();i++){if(isValid(s,startindex,i)){s.insert(s.begin()+i+1, '.');pointcount++;backtracking(s,i+2);s.erase(s.begin()+i+1);pointcount--;}else{break;}}}bool isValid(string &s,int startindex,int endindex){if(startindex>endindex)return false;if(s[startindex] == '0'&&startindex!=endindex)return false;string str = s.substr(startindex,endindex-startindex+1);int num = stoi(str);if(num <=255){return true;}return false;}vector<string> res;int pointcount;
};

子集问题

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的
子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

如果把 子集问题、组合问题、分割问题都抽象为一棵树的话,那么组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点!其实子集也是一种组合问题,因为它的集合是无序的,子集{1,2} 和 子集{2,1}是一样的。那么既然是无序,**取过的元素不会重复取,写回溯算法的时候,for就要从startIndex开始,而不是从0开始!**有同学问了,什么时候for可以从0开始呢?
求排列问题的时候,就要从0开始,因为集合是有序的,{1, 2} 和{2, 1}是两个集合,排列问题我们后续的文章就会讲到的。

【78】子集

class Solution {
public:vector<vector<int>> subsets(vector<int>& nums) {res.clear();path.clear();recurse(nums,0);return res;}
private:void recurse(vector<int>& nums,int startindex){res.push_back(path);//要放在上面,收集每个点if(startindex >=nums.size()){//没有剩余元素了 这里收集的是叶子节点return;}for(int i =startindex;i<nums.size();i++){path.push_back(nums[i]);recurse(nums,i+1);path.pop_back();}}vector<vector<int>> res;vector<int> path;
};

这道题目和78.子集 (opens new window)区别就是集合里有重复元素了,而且求取的子集要去重。

【90】子集II

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。返回的解集中,子集可以按任意顺序排列。
示例 1:
输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]

class Solution {
public:vector<vector<int>> subsetsWithDup(vector<int>& nums) {res.clear();path.clear();sort(nums.begin(),nums.end());backtracking(nums,0);return res;}
private:void backtracking(vector<int>& nums,int startindex){res.push_back(path);if(startindex > nums.size()){return;}for(int i =startindex;i<nums.size();i++){if(i >startindex && nums[i] == nums[i-1]){//重复的情况continue;}else{path.push_back(nums[i]);backtracking(nums,i+1);path.pop_back();}}}vector<vector<int>> res;vector<int> path;
};

排列问题

【46】全排列

  1. 全排列
    给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
    示例 1:
    输入:nums = [1,2,3]
    输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

重点是存一个used数组标记哪些元素是用过的
在这里插入图片描述

class Solution {
public:vector<vector<int>> permute(vector<int>& nums) {path.clear();res.clear();vector<int> used(nums.size());backtracking(nums,used);return res;}
private:void backtracking(vector<int>& nums,vector<int>& used){if(path.size() == nums.size()){//到叶子结点结束res.push_back(path);return;}for(int i = 0;i<nums.size();i++){//需要从0开始,因为每个元素都要遍历到if(used[i] != 1){used[i] = 1;path.push_back(nums[i]);backtracking(nums,used);used[i] = 0;path.pop_back();}else{continue;}}}vector<vector<int>> res;vector<int> path;
};

【47】全排列II

给定的是重复的数组,给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]

要包含不重复,就要用used数组去比较

【22】括号生成

全排列没有剪枝的情况。


class Solution {
public:vector<string> generateParenthesis(int n) {res.clear();path.clear();string str = "()";backtrack(str, n);return res;}private:void backtrack(string const &str, int n) {if (path.size() == n * 2) {if (isValid(path)) {res.push_back(path);}return;//都要return}for (int i = 0; i < 2; i++) {path.push_back(str[i]);backtrack(str, n);path.pop_back();}}bool isValid(string &path) {stack<char> stk;for (char ele : path) {if (ele == '(') {stk.push(ele);} else {if (stk.empty()) {return false;}stk.pop();}}return stk.empty();}vector<string> res;string path;
};

然后发现剪枝一下更快,右括号没有最好

class Solution {
public:vector<string> generateParenthesis(int n) {res.clear();path.clear();string str = "()";backtrack(n,0,0);return res;}private:void backtrack(int n,int open,int close) {if (path.size() == n * 2) {if (isValid(path)) {res.push_back(path);}return;//都要return}//不用for,直接分两种情况讨论,但是要统计括号的多少if(open < n){path.push_back('(');backtrack(n,open+1,close);path.pop_back();}if (close < open) {path.push_back(')');backtrack(n, open, close + 1);path.pop_back();}}bool isValid(string &path) {stack<char> stk;for (char ele : path) {if (ele == '(') {stk.push(ele);} else {if (stk.empty()) {return false;}stk.pop();}}return stk.empty();}vector<string> res;string path;
};

棋盘问题

【51】N皇后

​ 这里我明确给出了棋盘的宽度就是for循环的长度,递归的深度就是棋盘的高度,这样就可以套进回溯法的模板里了。
在这里插入图片描述

class Solution {
public:vector<vector<string>> solveNQueens(int n) {vector<string> path(n,string(n,'.'));backtracking(path,n,0);return res;}
private:void backtracking(vector<string> &path,int n,int row){if(row == n){res.push_back(path);return;}for(int col = 0;col<n;col++){//每一列横向遍历//在第几列就push进去if(isValid(path,n,row,col)){//验证合法就可以放进去path[row][col] = 'Q';//标记backtracking(path,n,row+1);//下一行path[row][col] = '.';//回溯}}}bool isValid(vector<string> &path,int n,int row,int col){//不能在同行for(int i =0;i<col;i++){if(path[row][i] == 'Q'){return false;}}//不能在同列for(int i =0;i<row;i++){if(path[i][col] == 'Q'){return false;}}//不同在同一条斜线上 45度 135度//135度for(int i = row-1,j = col-1;i>=0&&j>=0;i--,j--){if(path[i][j] == 'Q'){return false;}}//45度for(int i=row-1,j = col+1;i>=0&&j<n;i--,j++){if(path[i][j] == 'Q'){return false;}}return true;}vector<vector<string>> res;//所有可能的结果};

【37】解数独

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

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

相关文章

计算机图形学入门21:辐射度量学

1.前言 在使用Blinn-Phong着色模型的时候&#xff0c;定义了一个光的强度I(Intensity)&#xff0c;假如I等于10。那么I等于10是什么意思&#xff1f;它肯定有单位和物理意义。另一方面&#xff0c;whited-style光线追踪模型也不是准确的模型&#xff0c;因为做了很多假设&#…

致远互联FE协作办公平台 codeMoreWidget SQL注入致RCE漏洞复现

0x01 产品简介 致远互联FE协作办公平台是一款为企业提供全方位协同办公解决方案的产品。它集成了多个功能模块&#xff0c;旨在帮助企业实现高效的团队协作、信息共享和文档管理。 0x02 漏洞概述 致远互联FE协作办公平台 codeMoreWidget.jsp接口处存在SQL注入漏洞,未经授权攻…

Python数据分析案例47——笔记本电脑价格影响因素分析

案例背景 博主对电脑的价格和配置一直略有研究&#xff0c;正好最近也有笔记本电脑相关的数据&#xff0c;想着来做点分析吧&#xff0c;写成一个案例。基本上描述性统计&#xff0c;画图&#xff0c;分组聚合&#xff0c;机器学习&#xff0c;交叉验证&#xff0c;搜索超参数…

SpringBoot异常处理

一、自定义错误页面 SpringBoot默认的处理异常的机制&#xff1a;SpringBoot 默认的已经提供了一套处理异常的机制。一旦程序中出现了异常 SpringBoot 会向/error 的 url 发送请求。在 springBoot 中提供了一个叫 BasicErrorController 来处理/error 请求&#xff0c;然后跳转…

鸿蒙开发设备管理:【@ohos.distributedHardware.deviceManager (设备管理)】

设备管理 本模块提供分布式设备管理能力。 系统应用可调用接口实现如下功能&#xff1a; 注册和解除注册设备上下线变化监听发现周边不可信设备认证和取消认证设备查询可信设备列表查询本地设备信息&#xff0c;包括设备名称&#xff0c;设备类型和设备标识 说明&#xff1a…

IIC学习笔记(立创STMF4开发板)

目录 #I2C涉及相关知识 #I2C相关介绍 欢迎指正&#xff0c;希望对你&#xff0c;有所帮助&#xff01;&#xff01;&#xff01; 个人学习笔记&#xff0c;参考文献&#xff0c;链接最后&#xff01;&#xff01;&#xff01; #I2C涉及相关知识 SDA串行数据线&#xff1a; Ser…

昇思25天学习打卡营第11天|基于MindSpore通过GPT实现情感分类

学AI还能赢奖品&#xff1f;每天30分钟&#xff0c;25天打通AI任督二脉 (qq.com) 基于MindSpore通过GPT实现情感分类 %%capture captured_output # 实验环境已经预装了mindspore2.2.14&#xff0c;如需更换mindspore版本&#xff0c;可更改下面mindspore的版本号 !pip uninsta…

java基于微信小程序+mysql+RocketMQ开发的医院智能问诊系统源码 智能导诊系统 智能导诊小程序源码

java基于微信小程序mysqlRocketMQ开发的医院智能问诊系统源码 智能导诊系统 智能导诊小程序源码 医院导诊系统是一种基于互联网和定位技术的智能化服务系统&#xff0c;旨在为患者提供精准、便捷的医院内部导航和医疗就诊咨询服务。该系统整合了医院的各种医疗服务资源&#x…

【软件实施】软件实施概论

目录 软件实施概述定义主要工作软件项目的实施工作区别于一般的项目&#xff08;如&#xff1a;房地产工程项目&#xff09;软件实施的重要性挑战与对策软件项目实施的流程软件项目实施的周期 软件企业软件企业分类产品型软件企业业务特点产品型软件企业的分类产品型软件企业的…

PortSip测试

安装PBX 下载 免费下载 PortSIP PBX 安装PBX&#xff0c;安装后&#xff0c;运行 &#xff0c;默认用户是admin 密码是admin&#xff0c;然后配置IP 为192.168.0.189 设置域名为192.168.0.189 配置分机 添加分机&#xff0c;添加了10001、10002、9999 三个分机&#xff0c…

10分钟完成微信JSAPI支付对接过程-JAVA后端接口

引入架包 <dependency><groupId>com.github.javen205</groupId><artifactId>IJPay-WxPay</artifactId><version>${ijapy.version}</version></dependency>配置类 package com.joolun.web.config;import org.springframework.b…

【递归、搜索与回溯】记忆化搜索

记忆化搜索 1.记忆化搜索2.不同路径3.最长递增子序列4. 猜数字大小 II5.矩阵中的最长递增路径 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#xff0c;我们一起努力吧!&#x1f603;&#x1f603;…

5000字深入讲解:企业数字化转型优先从哪个板块开始?

很多企业都知道数字化转型重要&#xff0c;但不知道应该怎样入手&#xff0c;分哪些阶段。以下引用国内领先数字化服务商 织信Informat 的数字化转型方法论材料&#xff0c;且看看他们是如何看待数字化转型的&#xff1f;数字化转型应该从哪先开始&#xff1f;如何做&#xff1…

P1107 [BJWC2008] 雷涛的小猫

[BJWC2008] 雷涛的小猫 题目背景 原最大整数参见 P1012 题目描述 雷涛同学非常的有爱心&#xff0c;在他的宿舍里&#xff0c;养着一只因为受伤被救助的小猫&#xff08;当然&#xff0c;这样的行为是违反学生宿舍管理条例的&#xff09;。在他的照顾下&#xff0c;小猫很快…

阿里云服务器数据库迁云: 数据从传统到云端的安全之旅(WordPress个人博客实战教学)

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 一、 开始实战1.2创建实验资源1.3重置云服务器ECS的登录密码&#xff08;请记住密码&#xff09;1.4 设置安全组端口1…

Adobe Acrobat Pro或者Adobe Acrobat Reader取消多标签页显示,设置打开一个pdf文件对应一个窗口。

Windows系统&#xff1a;Adobe Acrobat Pro或者Adobe Acrobat Reader首选项-一般-取消在同一窗口的新标签中打开文档&#xff08;需要重启&#xff09;的对勾&#xff0c;点击确定&#xff0c;彻底关闭后重启&#xff0c;这样打开的每一个PDF文件对应的是一个窗口&#xff0c;并…

Bridging nonnull in Objective-C to Swift: Is It Safe?

Bridging nonnull in Objective-C to Swift: Is It Safe? In the world of iOS development, bridging between Objective-C and Swift is a common practice, especially for legacy codebases (遗留代码库) or when integrating (集成) third-party libraries. One importa…

重磅更新-UniApp自定义字体可视化设计

重磅更新-UniApp自定义字体可视化设计。 DIY可视化为了适配不同APP需要&#xff0c;支持用户自定义字体&#xff0c;自定义字体后&#xff0c;设计出来的界面更多样化&#xff0c;不再是单一字体效果。用户可以使用第三方字体加入设计&#xff0c;在设计的时候选择上自己的字体…

AI副业赚钱攻略:掌握数字时代的机会

前言 最近国产大模型纷纷上线&#xff0c;飞入寻常百姓家。AI副业正成为许多人寻找额外收入的途径。无论您是想提高家庭收入还是寻求职业发展&#xff0c;这里有一个变现&#xff0c;帮助您掌握AI兼职副业的机会。 1. 了解AI的基础知识 在开始之前&#xff0c;了解AI的基础…

一个开源的、独立的、可自托管的评论系统,专为现代Web平台设计

大家好&#xff0c;今天给大家分享的是一个开源的、独立的、可自托管的评论系统&#xff0c;专为现代Web平台设计。 Remark42是一个自托管的、轻量级的、简单的&#xff08;但功能强大的&#xff09;评论引擎&#xff0c;它不会监视用户。它可以嵌入到博客、文章或任何其他读者…