综合练习 —— 递归、搜索与回溯算法
目录
一、1863. 找出所有子集的异或总和再求和 - 力扣(LeetCode)
算法代码:
代码思路
问题分析
核心思想
实现细节
代码解析
初始化
DFS 函数
时间复杂度
空间复杂度
示例运行
输入
运行过程
总结
二、 47. 全排列 II - 力扣(LeetCode)
算法代码:
代码思路
问题分析
核心思想
实现细节
代码解析
排序
DFS 函数
剪枝条件
时间复杂度
空间复杂度
示例运行
输入
运行过程
总结
以下是 if 条件的详细解析:
if 条件
1. check[i] == false
2. i == 0
3. nums[i] != nums[i - 1]
4. check[i - 1] != false
if 条件的逻辑
示例说明
输入:
排序后:
运行过程:
三、17. 电话号码的字母组合 - 力扣(LeetCode)
算法代码:
代码思路解析
类的成员变量
主函数 letterCombinations
DFS 函数 dfs
代码优化建议
参数传递优化
提前终止
代码可读性
优化后的代码
总结
四、 22. 括号生成 - 力扣(LeetCode)
算法代码:(不传参)
代码思路解析
类的成员变量
主函数 generateParenthesis
DFS 函数 dfs
代码优化建议
参数传递优化
提前终止
代码可读性
优化后的代码
总结
算法代码:(传参)
五、77. 组合 - 力扣(LeetCode)
算法代码:
代码思路解析
类的成员变量
主函数 combine
DFS 函数 dfs
代码优化建议
剪枝优化
参数传递优化
代码可读性
优化后的代码
优化点详解
剪枝优化
回溯的恢复现场
总结
六、494. 目标和 - 力扣(LeetCode)
算法代码:
代码思路解析
类的成员变量
主函数 findTargetSumWays
DFS 函数 dfs
把 path 改为全局变量时会超时,为什么呢?
回溯的恢复现场问题
递归调用栈的深度
多线程问题
代码可读性和调试难度
问题分析:
总结
七、39. 组合总和 - 力扣(LeetCode)
解法一:算法代码(回溯)
代码思路解析
类的成员变量
主函数 combinationSum
DFS 函数 dfs
解法二:算法代码(无限次使用判断有无越界)
代码思路解析
类的成员变量
主函数 combinationSum
DFS 函数 dfs
代码优化建议
剪枝优化
恢复现场的优化
代码可读性
优化后的代码
代码细节解析
枚举 k 的作用
恢复现场
递归调用
总结
八、 784. 字母大小写全排列 - 力扣(LeetCode)
算法代码:
代码思路解析
类的成员变量
主函数 letterCasePermutation
DFS 函数 dfs
辅助函数 change
代码优化建议
减少函数调用
剪枝优化
代码可读性
优化后的代码
代码细节解析
不改变字符
改变字符大小写
剪枝优化
总结
九、526. 优美的排列 - 力扣(LeetCode)
算法代码:
代码思路解析
类的成员变量
主函数 countArrangement
DFS 函数 dfs
代码优化建议
剪枝优化
代码可读性
优化后的代码
代码细节解析
check 数组的作用
剪枝条件
回溯的恢复现场
总结
十、 51. N 皇后 - 力扣(LeetCode)
算法代码:
代码思路解析
类的成员变量
主函数 solveNQueens
DFS 函数 dfs
代码优化建议
剪枝优化
代码可读性
优化后的代码
代码细节解析
checkCol、checkDig1、checkDig2 的作用
放置皇后
回溯的恢复现场
总结
十一、36. 有效的数独 - 力扣(LeetCode)
算法代码:
代码思路解析
数据结构
遍历棋盘
检查有效性
返回结果
代码优化建议
总结:
十二、 37. 解数独 - 力扣(LeetCode)
算法代码:
代码思路解析
数据结构
初始化
深度优先搜索(DFS)
回溯
终止条件
代码优化建议
总结
十三、 79. 单词搜索 - 力扣(LeetCode)
算法代码:
代码思路解析
数据结构
主函数 exist
深度优先搜索(DFS)函数 dfs
回溯
代码优化建议
总结
十四、 1219. 黄金矿工 - 力扣(LeetCode)
算法代码:
代码思路解析
数据结构
主函数 getMaximumGold
深度优先搜索(DFS)函数 dfs
回溯
代码优化建议
总结
十五、980. 不同路径 III - 力扣(LeetCode)
算法代码:
代码思路解析
数据结构
主函数 uniquePathsIII
深度优先搜索(DFS)函数 dfs
回溯
代码优化建议
总结
一、1863. 找出所有子集的异或总和再求和 - 力扣(LeetCode)
算法代码:
class Solution {int path; // 当前子集的异或和int sum; // 所有子集异或和的总和public:int subsetXORSum(vector<int>& nums) {path = 0; // 初始化 pathsum = 0; // 初始化 sumdfs(nums, 0); // 从第 0 个元素开始 DFSreturn sum;}void dfs(vector<int>& nums, int pos) {sum += path; // 将当前子集的异或和累加到 sumfor (int i = pos; i < nums.size(); i++) {path ^= nums[i]; // 将 nums[i] 加入当前子集,更新 pathdfs(nums, i + 1); // 递归处理下一个元素path ^= nums[i]; // 回溯,恢复 path 的值}}
};
代码思路
-
问题分析
-
给定一个数组
nums
,需要计算所有子集的异或和的总和。 -
例如,
nums = [1, 2, 3]
,子集包括[], [1], [2], [3], [1,2], [1,3], [2,3], [1,2,3]
,它们的异或和分别为0, 1, 2, 3, 3, 2, 1, 0
,总和为0 + 1 + 2 + 3 + 3 + 2 + 1 + 0 = 12
。
-
-
核心思想
-
使用深度优先搜索(DFS)遍历所有可能的子集。
-
在 DFS 过程中,维护一个变量
path
,表示当前子集的异或和。 -
每次递归时,将当前子集的异或和
path
累加到sum
中。 -
通过回溯恢复现场,确保
path
的正确性。
-
-
实现细节
-
path
:表示当前子集的异或和。 -
sum
:表示所有子集异或和的总和。 -
dfs
函数:-
将当前
path
的值累加到sum
。 -
遍历数组
nums
,从当前位置pos
开始,逐个尝试将元素加入当前子集。 -
递归调用
dfs
,继续处理下一个元素。 -
回溯时,恢复
path
的值(通过再次异或当前元素)。
-
-
代码解析
-
初始化
-
path
和sum
初始化为 0。
-
-
DFS 函数
-
累加当前子集的异或和:
sum += path
。 -
遍历数组:
-
将当前元素
nums[i]
异或到path
中,表示将其加入当前子集。 -
递归调用
dfs
,处理下一个元素。 -
回溯时,再次异或
nums[i]
,恢复path
的值。
-
-
-
时间复杂度
-
由于每个元素有两种选择(加入或不加入子集),总共有 2n 个子集,因此时间复杂度为 O(2n)。
-
-
空间复杂度
-
递归栈的深度为 O(n),因此空间复杂度为 O(n)。
-
示例运行
输入
nums = [1, 2, 3];
运行过程
-
初始状态:
path = 0
,sum = 0
。 -
DFS 遍历所有子集:
-
[]
:path = 0
,sum += 0
。 -
[1]
:path = 1
,sum += 1
。 -
[1, 2]
:path = 3
,sum += 3
。 -
[1, 2, 3]
:path = 0
,sum += 0
。 -
[1, 3]
:path = 2
,sum += 2
。 -
[2]
:path = 2
,sum += 2
。 -
[2, 3]
:path = 1
,sum += 1
。 -
[3]
:path = 3
,sum += 3
。
-
-
最终结果:
sum = 0 + 1 + 3 + 0 + 2 + 2 + 1 + 3 = 12
。
总结
-
这段代码通过 DFS 和回溯,高效地计算了所有子集的异或和的总和。
-
核心思想是利用递归遍历所有可能的子集,并通过回溯恢复现场,确保计算的正确性。
-
代码简洁清晰,适合解决类似子集相关的问题。
二、 47. 全排列 II - 力扣(LeetCode)
算法代码:
class Solution {vector<int> path; // 当前生成的排列vector<vector<int>> ret; // 所有不重复的排列bool check[9]; // 记录元素是否被使用过public:vector<vector<int>> permuteUnique(vector<int>& nums) {sort(nums.begin(), nums.end()); // 排序,方便剪枝dfs(nums, 0); // 从第 0 个位置开始 DFSreturn ret;}void dfs(vector<int>& nums, int pos) {if (pos == nums.size()) {ret.push_back(path); // 当前排列完成,加入结果集return;}for (int i = 0; i < nums.size(); i++) {// 剪枝条件if (check[i] == false &&(i == 0 || nums[i] != nums[i - 1] || check[i - 1] != false)) {path.push_back(nums[i]); // 将 nums[i] 加入当前排列check[i] = true; // 标记 nums[i] 已被使用dfs(nums, pos + 1); // 递归处理下一个位置path.pop_back(); // 回溯,恢复 pathcheck[i] = false; // 回溯,恢复 check}}}
};
代码思路
-
问题分析
-
给定一个可能包含重复元素的数组
nums
,需要生成所有不重复的全排列。 -
例如,
nums = [1, 1, 2]
,不重复的全排列为[[1,1,2], [1,2,1], [2,1,1]]
。
-
-
核心思想
-
使用深度优先搜索(DFS)生成所有可能的排列。
-
通过排序和剪枝,避免生成重复的排列。
-
使用一个布尔数组
check
记录哪些元素已经被使用过。
-
-
实现细节
-
path
:记录当前生成的排列。 -
ret
:存储所有不重复的排列。 -
check
:记录每个元素是否已经被使用。 -
dfs
函数:-
如果当前排列的长度等于
nums
的长度,将其加入ret
。 -
遍历数组
nums
,尝试将未使用的元素加入当前排列。 -
通过剪枝条件避免重复排列。
-
回溯时恢复现场,确保
path
和check
的正确性。
-
-
代码解析
-
排序
-
对数组
nums
进行排序,使得相同的元素相邻,方便剪枝。
-
-
DFS 函数
-
终止条件:如果当前排列的长度等于
nums
的长度,将其加入ret
。 -
遍历数组:
-
如果当前元素未被使用(
check[i] == false
),并且满足剪枝条件,则将其加入当前排列。 -
递归调用
dfs
,处理下一个位置。 -
回溯时,恢复
path
和check
的值。
-
-
-
剪枝条件
-
i == 0
:第一个元素无需判断是否重复。 -
nums[i] != nums[i - 1]
:当前元素与前一个元素不同,无需剪枝。 -
check[i - 1] != false
:前一个相同元素已被使用,说明当前元素是新的排列起点。
-
-
时间复杂度
-
最坏情况下,所有排列都不重复,时间复杂度为 O(n×n!)O(n×n!),其中 n!n! 是排列数,nn 是生成每个排列的时间。
-
-
空间复杂度
-
递归栈的深度为 O(n)O(n),结果集
ret
的空间为 O(n×n!)O(n×n!)。
-
示例运行
输入
nums = [1, 1, 2];
运行过程
-
排序后:
nums = [1, 1, 2]
。 -
DFS 遍历:
-
生成
[1, 1, 2]
。 -
生成
[1, 2, 1]
。 -
生成
[2, 1, 1]
。
-
-
最终结果:
ret = [[1,1,2], [1,2,1], [2,1,1]]
。
总结
-
这段代码通过 DFS 和剪枝,高效地生成了所有不重复的全排列。
-
核心思想是利用排序和剪枝条件,避免重复排列的生成。
-
代码简洁清晰,适合解决类似排列相关的问题。
在代码中,if
条件的写法是为了避免生成重复的排列,尤其是在数组 nums
包含重复元素的情况下。这个条件的作用是剪枝,即跳过不必要的递归分支,从而减少重复计算。
以下是 if
条件的详细解析:
if
条件
if (check[i] == false &&(i == 0 || nums[i] != nums[i - 1] || check[i - 1] != false))
1. check[i] == false
-
作用:确保当前元素
nums[i]
未被使用过。 -
解释:
-
check[i]
是一个布尔数组,记录nums[i]
是否已经被加入当前排列。 -
如果
check[i] == true
,说明nums[i]
已经被使用过,不能重复使用。
-
2. i == 0
-
作用:处理第一个元素,避免越界。
-
解释:
-
当
i == 0
时,nums[i - 1]
不存在,因此不需要判断nums[i] != nums[i - 1]
或check[i - 1] != false
。
-
3. nums[i] != nums[i - 1]
-
作用:确保当前元素与前一个元素不同。
-
解释:
-
如果
nums[i] == nums[i - 1]
,说明当前元素和前一个元素相同。 -
为了避免重复排列,只有当当前元素与前一个元素不同时,才继续递归。
-
4. check[i - 1] != false
-
作用:确保前一个相同元素已经被使用过。
-
解释:
-
如果
nums[i] == nums[i - 1]
,并且check[i - 1] == false
,说明前一个相同元素未被使用过。 -
这种情况下,如果直接使用
nums[i]
,会导致重复排列。 -
只有当
check[i - 1] != false
(即前一个相同元素已经被使用过),才允许使用nums[i]
。
-
if
条件的逻辑
-
整体逻辑:
-
如果当前元素未被使用过(
check[i] == false
),并且满足以下条件之一:-
当前元素是第一个元素(
i == 0
)。 -
当前元素与前一个元素不同(
nums[i] != nums[i - 1]
)。 -
前一个相同元素已经被使用过(
check[i - 1] != false
)。
-
-
则继续递归,否则跳过。
-
-
为什么这样写:
-
通过排序,相同的元素会相邻。
-
对于相同的元素,只有当前一个相同元素已经被使用过时,才允许使用当前元素。
-
这样可以避免生成重复的排列。
-
示例说明
输入:
nums = [1, 1, 2];
排序后:
nums = [1, 1, 2];
运行过程:
-
第一次递归:
-
选择
nums[0] = 1
,check[0] = true
。 -
进入下一层递归。
-
-
第二次递归:
-
选择
nums[1] = 1
,此时nums[1] == nums[0]
,但check[0] == true
,因此允许选择。 -
check[1] = true
。 -
进入下一层递归。
-
-
第三次递归:
-
选择
nums[2] = 2
,check[2] = true
。 -
生成排列
[1, 1, 2]
。
-
-
回溯:
-
恢复
check[2] = false
。 -
恢复
check[1] = false
。 -
恢复
check[0] = false
。
-
-
第二次递归(另一种选择):
-
选择
nums[2] = 2
,check[2] = true
。 -
进入下一层递归。
-
-
第三次递归:
-
选择
nums[0] = 1
,check[0] = true
。 -
生成排列
[1, 2, 1]
。
-
-
回溯:
-
恢复
check[0] = false
。 -
恢复
check[2] = false
。
-
-
继续递归:
-
类似过程生成
[2, 1, 1]
。
-
三、17. 电话号码的字母组合 - 力扣(LeetCode)
算法代码:
class Solution {string hash[10] = {"", "", "abc", "def", "ghi","jkl", "mno", "pqrs", "tuv", "wxyz"};string path;vector<string> ret;public:vector<string> letterCombinations(string digits) {if (digits.size() == 0)return ret;dfs(digits, 0);return ret;}void dfs(string& digits, int pos) {if (pos == digits.size()) {ret.push_back(path);return;}for (auto ch : hash[digits[pos] - '0']) {path.push_back(ch);dfs(digits, pos + 1);path.pop_back(); // 恢复现场}}
};
这段代码的目的是生成给定数字字符串 digits
所代表的所有可能的字母组合。例如,输入 "23"
,输出 ["ad","ae","af","bd","be","bf","cd","ce","cf"]
。代码使用了深度优先搜索(DFS)来遍历所有可能的组合。
代码思路解析
-
类的成员变量
-
hash[10]
:一个字符串数组,存储了每个数字对应的字母。例如,2
对应"abc"
,3
对应"def"
,依此类推。 -
path
:一个字符串,用于存储当前正在构建的字母组合。 -
ret
:一个字符串向量,用于存储所有可能的字母组合。
-
-
主函数
letterCombinations
-
首先检查输入字符串
digits
是否为空。如果为空,直接返回空的ret
。 -
否则,调用
dfs
函数开始深度优先搜索。
-
-
DFS 函数
dfs
-
pos
参数表示当前处理到digits
中的第几个字符。 -
如果
pos
等于digits
的长度,说明已经处理完所有字符,当前的path
就是一个完整的字母组合,将其加入ret
中。 -
否则,遍历当前数字对应的所有字母(通过
hash[digits[pos] - '0']
获取),并将每个字母依次加入path
中,然后递归调用dfs
处理下一个字符。 -
递归调用结束后,通过
path.pop_back()
恢复现场,以便尝试下一个字母。
-
代码优化建议
-
参数传递优化
-
digits
和path
可以通过引用传递,避免不必要的拷贝。
-
-
提前终止
-
如果
digits
为空,可以直接返回空结果,不需要进入 DFS。
-
-
代码可读性
-
可以添加注释,解释每个步骤的作用,方便他人理解。
-
优化后的代码
class Solution {// 数字到字母的映射const string hash[10] = {"", "", "abc", "def", "ghi","jkl", "mno", "pqrs", "tuv", "wxyz"};string path; // 当前路径vector<string> ret; // 结果集public:vector<string> letterCombinations(string digits) {if (digits.empty()) {return ret; // 如果输入为空,直接返回空结果}dfs(digits, 0); // 开始深度优先搜索return ret;}void dfs(const string& digits, int pos) {if (pos == digits.size()) {ret.push_back(path); // 找到一个完整的组合,加入结果集return;}// 遍历当前数字对应的所有字母for (char ch : hash[digits[pos] - '0']) {path.push_back(ch); // 选择当前字母dfs(digits, pos + 1); // 递归处理下一个数字path.pop_back(); // 回溯,撤销选择}}
};
总结
这段代码的核心思想是通过 DFS 遍历所有可能的字母组合,并通过回溯来恢复现场,确保每个组合都被正确生成。代码结构清晰,逻辑简单,适合处理这类组合问题。
四、 22. 括号生成 - 力扣(LeetCode)
算法代码:(不传参)
class Solution {int left, right, n;string path;vector<string> ret;public:vector<string> generateParenthesis(int _n) {n = _n;dfs();return ret;}void dfs() {if (right == n) {ret.push_back(path);return;}if (left < n) // 添加左括号{path.push_back('(');left++;dfs();path.pop_back();left--; // 恢复现场}if (right < left) // 添加右括号{path.push_back(')');right++;dfs();path.pop_back();right--; // 恢复现场}}
};
这段代码的目的是生成所有有效的括号组合,给定一个整数 n
,表示生成 n
对括号。例如,当 n = 3
时,输出 ["((()))","(()())","(())()","()(())","()()()"]
。代码使用了深度优先搜索(DFS)和回溯的思想来遍历所有可能的组合。
代码思路解析
-
类的成员变量
-
left
:记录当前路径中左括号的数量。 -
right
:记录当前路径中右括号的数量。 -
n
:表示需要生成的括号对数。 -
path
:一个字符串,用于存储当前正在构建的括号组合。 -
ret
:一个字符串向量,用于存储所有有效的括号组合。
-
-
主函数
generateParenthesis
-
初始化
n
为输入的_n
。 -
调用
dfs
函数开始深度优先搜索。 -
返回结果
ret
。
-
-
DFS 函数
dfs
-
如果
right == n
,说明当前路径中的右括号数量已经达到n
,即已经生成了一个有效的括号组合,将其加入ret
中。 -
如果
left < n
,说明还可以添加左括号:-
将左括号
(
加入path
中。 -
增加
left
的计数。 -
递归调用
dfs
。 -
回溯时,移除刚刚添加的左括号,并减少
left
的计数。
-
-
如果
right < left
,说明可以添加右括号:-
将右括号
)
加入path
中。 -
增加
right
的计数。 -
递归调用
dfs
。 -
回溯时,移除刚刚添加的右括号,并减少
right
的计数。
-
-
代码优化建议
-
参数传递优化
-
path
可以通过引用传递,避免不必要的拷贝。
-
-
提前终止
-
如果
left
或right
超过n
,可以直接返回,避免无效的递归调用。
-
-
代码可读性
-
可以添加注释,解释每个步骤的作用,方便他人理解。
-
优化后的代码
class Solution {int left, right, n; // left: 当前左括号数量,right: 当前右括号数量,n: 总括号对数string path; // 当前路径vector<string> ret; // 结果集public:vector<string> generateParenthesis(int _n) {n = _n;dfs(); // 开始深度优先搜索return ret;}void dfs() {if (right == n) {ret.push_back(path); // 找到一个有效的括号组合,加入结果集return;}if (left < n) { // 可以添加左括号path.push_back('('); // 选择左括号left++; // 增加左括号计数dfs(); // 递归处理path.pop_back(); // 回溯,撤销选择left--; // 恢复左括号计数}if (right < left) { // 可以添加右括号path.push_back(')'); // 选择右括号right++; // 增加右括号计数dfs(); // 递归处理path.pop_back(); // 回溯,撤销选择right--; // 恢复右括号计数}}
};
总结
这段代码的核心思想是通过 DFS 遍历所有可能的括号组合,并通过回溯来恢复现场,确保每个组合都是有效的。代码结构清晰,逻辑简单,适合处理这类组合问题。通过限制左括号和右括号的数量,确保生成的括号组合始终是有效的。
算法代码:(传参)
class Solution {vector<string> ret; // 结果集public:vector<string> generateParenthesis(int n) {dfs(0, 0, n, ""); // 开始深度优先搜索return ret;}void dfs(int left, int right, int n, string path) {if (right == n) {ret.push_back(path); // 找到一个有效的括号组合,加入结果集return;}if (left < n) { // 可以添加左括号dfs(left + 1, right, n, path + '('); // 选择左括号,递归处理}if (right < left) { // 可以添加右括号dfs(left, right + 1, n, path + ')'); // 选择右括号,递归处理}}
};
五、77. 组合 - 力扣(LeetCode)
算法代码:
class Solution {vector<int> path;vector<vector<int>> ret;int n, k;public:vector<vector<int>> combine(int _n, int _k) {n = _n;k = _k;dfs(1);return ret;}void dfs(int start) {if (path.size() == k) {ret.push_back(path);return;}for (int i = start; i <= n; i++) {path.push_back(i);dfs(i + 1);path.pop_back(); // 恢复现场}}
};
这段代码的目的是生成从 1
到 n
中所有可能的 k
个数的组合。例如,当 n = 4
,k = 2
时,输出 [[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]
。代码使用了深度优先搜索(DFS)和回溯的思想来遍历所有可能的组合。
代码思路解析
-
类的成员变量
-
path
:一个整数向量,用于存储当前正在构建的组合。 -
ret
:一个二维整数向量,用于存储所有可能的组合。 -
n
:表示数字范围的上限(从1
到n
)。 -
k
:表示每个组合中数字的个数。
-
-
主函数
combine
-
初始化
n
和k
为输入的_n
和_k
。 -
调用
dfs(1)
开始深度优先搜索,从数字1
开始。 -
返回结果
ret
。
-
-
DFS 函数
dfs
-
start
参数表示当前可以选择的起始数字。 -
如果
path.size() == k
,说明当前路径中的数字数量已经达到k
,即已经生成了一个有效的组合,将其加入ret
中。 -
否则,从
start
开始遍历到n
:-
将当前数字
i
加入path
中。 -
递归调用
dfs(i + 1)
,确保下一个数字比当前数字大,避免重复组合。 -
回溯时,移除刚刚添加的数字
i
,恢复现场。
-
-
代码优化建议
-
剪枝优化
-
在遍历时,如果剩余的数字不足以填满
k
个数的组合,可以直接跳过。例如,当path.size() + (n - i + 1) < k
时,可以直接break
。
-
-
参数传递优化
-
path
可以通过引用传递,避免不必要的拷贝。
-
-
代码可读性
-
可以添加注释,解释每个步骤的作用,方便他人理解。
-
优化后的代码
class Solution {vector<int> path; // 当前路径vector<vector<int>> ret; // 结果集int n, k; // n: 数字范围上限,k: 组合中数字的个数public:vector<vector<int>> combine(int _n, int _k) {n = _n;k = _k;dfs(1); // 从数字 1 开始深度优先搜索return ret;}void dfs(int start) {if (path.size() == k) {ret.push_back(path); // 找到一个有效的组合,加入结果集return;}// 剪枝:如果剩余的数字不足以填满 k 个数的组合,直接返回for (int i = start; i <= n - (k - path.size()) + 1; i++) {path.push_back(i); // 选择当前数字dfs(i + 1); // 递归处理下一个数字path.pop_back(); // 回溯,撤销选择}}
};
优化点详解
-
剪枝优化
-
在
for
循环中,i
的上限从n
改为n - (k - path.size()) + 1
。 -
这是因为如果剩余的数字不足以填满
k
个数的组合,继续遍历是没有意义的。 -
例如,当
n = 4
,k = 2
,且path.size() = 1
时,i
的最大值应该是3
(因为4
只能和5
组合,但5
超出了范围)。
-
-
回溯的恢复现场
-
在递归调用结束后,通过
path.pop_back()
移除刚刚添加的数字,确保path
恢复到之前的状态。
-
总结
这段代码的核心思想是通过 DFS 遍历所有可能的组合,并通过回溯来恢复现场,确保每个组合都是唯一的。通过剪枝优化,可以减少不必要的递归调用,提高代码效率。代码结构清晰,逻辑简单,适合处理这类组合问题。
六、494. 目标和 - 力扣(LeetCode)
算法代码:
class Solution {int ret, aim; // ret: 满足条件的组合数量,aim: 目标值public:int findTargetSumWays(vector<int>& nums, int target) {aim = target;dfs(nums, 0, 0); // 从数组的第一个位置和初始路径和开始return ret;}void dfs(vector<int>& nums, int pos, int path) {if (pos == nums.size()) { // 已经处理完所有数字if (path == aim) { // 如果当前路径和等于目标值ret++; // 增加满足条件的组合数量}return;}// 加法dfs(nums, pos + 1, path + nums[pos]);// 减法dfs(nums, pos + 1, path - nums[pos]);}
};
这段代码的目的是在给定一个整数数组 nums
和一个目标值 target
的情况下,计算有多少种不同的方式可以通过在数组中的每个数字前添加 +
或 -
,使得表达式的值等于 target
。例如,nums = [1,1,1,1,1]
,target = 3
,输出 5
。
代码使用了深度优先搜索(DFS)来遍历所有可能的加减组合,并通过回溯的方式统计满足条件的组合数量。
代码思路解析
-
类的成员变量
-
ret
:用于记录满足条件的组合数量。 -
aim
:存储目标值target
。
-
-
主函数
findTargetSumWays
-
初始化
aim
为target
。 -
调用
dfs(nums, 0, 0)
开始深度优先搜索,从数组的第一个位置(pos = 0
)和初始路径和(path = 0
)开始。 -
返回结果
ret
。
-
-
DFS 函数
dfs
-
nums
:输入的整数数组。 -
pos
:当前处理到的数组位置。 -
path
:当前路径的和(即当前表达式的值)。 -
如果
pos == nums.size()
,说明已经处理完所有数字,检查当前路径和是否等于aim
。如果相等,ret++
。 -
否则,分别尝试对当前数字进行加法和减法操作,并递归调用
dfs
。
-
class Solution {int ret, aim, path; // path 改为全局变量public:int findTargetSumWays(vector<int>& nums, int target) {aim = target;path = 0; // 初始化 pathdfs(nums, 0);return ret;}void dfs(vector<int>& nums, int pos) {if (pos == nums.size()) {if (path == aim) {ret++;}return;}// 加法path += nums[pos]; // 修改全局变量 pathdfs(nums, pos + 1);path -= nums[pos]; // 恢复现场// 减法path -= nums[pos]; // 修改全局变量 pathdfs(nums, pos + 1);path += nums[pos]; // 恢复现场}
};
把 path
改为全局变量时会超时,为什么呢?
如果将 path
改为全局变量(即类的成员变量),代码可能会超时,原因如下:
-
回溯的恢复现场问题
-
在当前的实现中,
path
是通过参数传递的,每次递归调用都会创建一个新的path
值。这样,在回溯时不需要手动恢复path
的状态。 -
如果将
path
改为全局变量,每次递归调用都会修改同一个path
变量。在回溯时,必须手动恢复path
的状态(例如,path -= nums[pos]
或path += nums[pos]
),否则会导致状态混乱。
-
-
递归调用栈的深度
-
如果
path
是全局变量,每次递归调用都会修改同一个变量,这会导致递归调用栈的深度增加,从而增加时间和空间复杂度。 -
而通过参数传递
path
,每次递归调用都会创建一个新的path
值,递归调用栈的深度不会增加。
-
-
多线程问题
-
如果代码在多线程环境中运行,全局变量
path
可能会导致线程安全问题。而通过参数传递path
,每个线程可以维护自己的状态。
-
-
代码可读性和调试难度
-
使用全局变量
path
会使代码的逻辑变得复杂,尤其是在回溯时需要手动恢复状态。这会增加调试的难度。 -
通过参数传递
path
,代码的逻辑更加清晰,调试也更加方便。
-
问题分析:
-
每次递归调用都会修改全局变量
path
,导致回溯时需要手动恢复状态。 -
如果忘记恢复状态,会导致错误的结果。
-
递归调用栈的深度增加,可能导致超时。
总结
-
path
作为参数:每次递归调用都会创建一个新的path
值,回溯时不需要手动恢复状态,代码逻辑清晰,效率高。 -
path
作为全局变量:需要手动恢复状态,容易出错,递归调用栈深度增加,可能导致超时。
因此,在这种回溯问题中,推荐将 path
作为参数传递,而不是作为全局变量。
七、39. 组合总和 - 力扣(LeetCode)
解法一:算法代码(回溯)
class Solution {vector<vector<int>> ret; // 存储所有满足条件的组合vector<int> path; // 当前路径public:vector<vector<int>> combinationSum(vector<int>& nums, int target) {dfs(nums, target, 0); // 开始深度优先搜索return ret;}void dfs(vector<int>& nums, int target, int start) {if (target == 0) { // 如果目标值为0,说明当前路径满足条件ret.push_back(path);return;}if (target < 0) { // 如果目标值小于0,说明当前路径不满足条件return;}for (int i = start; i < nums.size(); i++) { // 从start开始遍历数组path.push_back(nums[i]); // 选择当前数字dfs(nums, target - nums[i], i); // 递归处理,注意可以重复选择当前数字path.pop_back(); // 回溯,撤销选择}}
};
代码思路解析
-
类的成员变量
-
ret
:用于存储所有满足条件的组合。 -
path
:用于存储当前正在构建的组合。
-
-
主函数
combinationSum
-
调用
dfs(nums, target, 0)
开始深度优先搜索,从数组的第一个位置开始。
-
-
DFS 函数
dfs
-
nums
:输入的整数数组。 -
target
:当前剩余的目标值。 -
start
:当前处理到的数组位置。 -
如果
target == 0
,说明当前路径的和等于目标值,将path
加入ret
中。 -
如果
target < 0
,说明当前路径的和已经超过目标值,直接返回。 -
否则,从
start
开始遍历数组:-
将当前数字
nums[i]
加入path
中。 -
递归调用
dfs(nums, target - nums[i], i)
,允许重复选择当前数字。 -
回溯时,移除刚刚添加的数字
nums[i]
,恢复现场。
-
-
解法二:算法代码(无限次使用判断有无越界)
class Solution {int aim;vector<int> path;vector<vector<int>> ret;public:vector<vector<int>> combinationSum(vector<int>& nums, int target) {aim = target;dfs(nums, 0, 0);return ret;}void dfs(vector<int>& nums, int pos, int sum) {if (sum == aim) {ret.push_back(path);return;}if (sum > aim || pos == nums.size())return;// 枚举个数for (int k = 0; k * nums[pos] + sum <= aim; k++) {if (k)path.push_back(nums[pos]);dfs(nums, pos + 1, sum + k * nums[pos]);}// 恢复现场for (int k = 1; k * nums[pos] + sum <= aim; k++) {path.pop_back();}}
};
这段代码的目的是在给定一个无重复元素的整数数组 nums
和一个目标值 target
的情况下,找出所有满足和为 target
的组合。数组中的数字可以无限次使用(即允许重复选择)。例如,nums = [2,3,6,7]
,target = 7
,输出 [[2,2,3],[7]]
。
代码使用了深度优先搜索(DFS)和回溯的思想来遍历所有可能的组合,并通过剪枝优化减少不必要的递归调用。
代码思路解析
-
类的成员变量
-
aim
:存储目标值target
。 -
path
:一个整数向量,用于存储当前正在构建的组合。 -
ret
:一个二维整数向量,用于存储所有满足条件的组合。
-
-
主函数
combinationSum
-
初始化
aim
为target
。 -
调用
dfs(nums, 0, 0)
开始深度优先搜索,从数组的第一个位置(pos = 0
)和初始和(sum = 0
)开始。 -
返回结果
ret
。
-
-
DFS 函数
dfs
-
nums
:输入的整数数组。 -
pos
:当前处理到的数组位置。 -
sum
:当前路径的和。 -
如果
sum == aim
,说明当前路径的和等于目标值,将path
加入ret
中。 -
如果
sum > aim
或pos == nums.size()
,说明当前路径的和已经超过目标值,或者已经处理完所有数字,直接返回。 -
否则,枚举当前数字
nums[pos]
的使用次数k
:-
如果
k > 0
,将nums[pos]
加入path
中。 -
递归调用
dfs(nums, pos + 1, sum + k * nums[pos])
,处理下一个数字。
-
-
回溯时,恢复现场,将
nums[pos]
从path
中移除。
-
代码优化建议
-
剪枝优化
-
在枚举
k
时,可以通过k * nums[pos] + sum <= aim
来限制k
的最大值,避免不必要的递归调用。
-
-
恢复现场的优化
-
在回溯时,可以通过一个循环将
nums[pos]
从path
中移除,确保path
恢复到之前的状态。
-
-
代码可读性
-
可以添加注释,解释每个步骤的作用,方便他人理解。
-
优化后的代码
class Solution {int aim; // 目标值vector<int> path; // 当前路径vector<vector<int>> ret; // 结果集public:vector<vector<int>> combinationSum(vector<int>& nums, int target) {aim = target;dfs(nums, 0, 0); // 从数组的第一个位置和初始和开始return ret;}void dfs(vector<int>& nums, int pos, int sum) {if (sum == aim) { // 当前路径和等于目标值ret.push_back(path); // 将当前路径加入结果集return;}if (sum > aim || pos == nums.size()) { // 剪枝:当前路径和超过目标值,或者已经处理完所有数字return;}// 枚举当前数字的使用次数for (int k = 0; k * nums[pos] + sum <= aim; k++) {if (k > 0) { // 如果 k > 0,将当前数字加入路径path.push_back(nums[pos]);}dfs(nums, pos + 1, sum + k * nums[pos]); // 递归处理下一个数字}// 恢复现场:将当前数字从路径中移除for (int k = 1; k * nums[pos] + sum <= aim; k++) {path.pop_back();}}
};
代码细节解析
-
枚举
k
的作用-
k
表示当前数字nums[pos]
的使用次数。 -
通过
k * nums[pos] + sum <= aim
限制k
的最大值,避免不必要的递归调用。
-
-
恢复现场
-
在回溯时,通过一个循环将
nums[pos]
从path
中移除,确保path
恢复到之前的状态。
-
-
递归调用
-
每次递归调用都会处理下一个数字(
pos + 1
),并更新当前路径和(sum + k * nums[pos]
)。
-
总结
这段代码的核心思想是通过 DFS 遍历所有可能的组合,并通过回溯来恢复现场,确保每个组合都是有效的。通过枚举 k
来限制当前数字的使用次数,并通过剪枝优化减少不必要的递归调用。代码结构清晰,逻辑简单,适合处理这类组合问题。
八、 784. 字母大小写全排列 - 力扣(LeetCode)
算法代码:
class Solution {string path;vector<string> ret;public:vector<string> letterCasePermutation(string s) {dfs(s, 0);return ret;}void dfs(string& s, int pos) {if (pos == s.length()) {ret.push_back(path);return;}char ch = s[pos];// 不改变path.push_back(ch);dfs(s, pos + 1);path.pop_back(); // 恢复现场// 改变if (ch < '0' || ch > '9') {char tmp = change(ch);path.push_back(tmp);dfs(s, pos + 1);path.pop_back(); // 恢复现场}}char change(char ch) {if (ch >= 'a' && ch <= 'z')ch -= 32;elsech += 32;return ch;}
};
这段代码的目的是生成给定字符串 s
的所有可能的字母大小写排列组合。例如,输入 s = "a1b2"
,输出 ["a1b2","a1B2","A1b2","A1B2"]
。代码使用了深度优先搜索(DFS)和回溯的思想来遍历所有可能的组合。
代码思路解析
-
类的成员变量
-
path
:一个字符串,用于存储当前正在构建的排列组合。 -
ret
:一个字符串向量,用于存储所有可能的排列组合。
-
-
主函数
letterCasePermutation
-
调用
dfs(s, 0)
开始深度优先搜索,从字符串的第一个字符(pos = 0
)开始。 -
返回结果
ret
。
-
-
DFS 函数
dfs
-
s
:输入的字符串。 -
pos
:当前处理到的字符位置。 -
如果
pos == s.length()
,说明已经处理完所有字符,当前的path
就是一个完整的排列组合,将其加入ret
中。 -
否则,获取当前字符
ch = s[pos]
:-
不改变字符:
-
将当前字符
ch
加入path
中。 -
递归调用
dfs(s, pos + 1)
,处理下一个字符。 -
回溯时,移除刚刚添加的字符
ch
,恢复现场。
-
-
改变字符大小写:
-
如果当前字符是字母(不是数字),调用
change(ch)
函数改变其大小写。 -
将改变后的字符加入
path
中。 -
递归调用
dfs(s, pos + 1)
,处理下一个字符。 -
回溯时,移除刚刚添加的字符,恢复现场。
-
-
-
-
辅助函数
change
-
如果字符是小写字母(
a-z
),将其转换为大写字母。 -
如果字符是大写字母(
A-Z
),将其转换为小写字母。 -
返回改变后的字符。
-
代码优化建议
-
减少函数调用
-
可以将
change
函数的逻辑直接嵌入到dfs
函数中,减少函数调用的开销。
-
-
剪枝优化
-
如果当前字符是数字,直接跳过大小写转换的逻辑,避免不必要的递归调用。
-
-
代码可读性
-
可以添加注释,解释每个步骤的作用,方便他人理解。
-
优化后的代码
class Solution {string path; // 当前路径vector<string> ret; // 结果集public:vector<string> letterCasePermutation(string s) {dfs(s, 0); // 从字符串的第一个字符开始深度优先搜索return ret;}void dfs(string& s, int pos) {if (pos == s.length()) { // 已经处理完所有字符ret.push_back(path); // 将当前路径加入结果集return;}char ch = s[pos]; // 获取当前字符// 不改变字符path.push_back(ch); // 选择当前字符dfs(s, pos + 1); // 递归处理下一个字符path.pop_back(); // 回溯,撤销选择// 改变字符大小写(如果是字母)if (isalpha(ch)) { // 剪枝:只处理字母char tmp = (ch >= 'a' && ch <= 'z') ? ch - 32 : ch + 32; // 改变大小写path.push_back(tmp); // 选择改变后的字符dfs(s, pos + 1); // 递归处理下一个字符path.pop_back(); // 回溯,撤销选择}}
};
代码细节解析
-
不改变字符
-
将当前字符
ch
加入path
中。 -
递归处理下一个字符。
-
回溯时,移除刚刚添加的字符
ch
。
-
-
改变字符大小写
-
如果当前字符是字母,调用
change
函数改变其大小写。 -
将改变后的字符加入
path
中。 -
递归处理下一个字符。
-
回溯时,移除刚刚添加的字符。
-
-
剪枝优化
-
如果当前字符是数字,直接跳过大小写转换的逻辑,避免不必要的递归调用。
-
总结
这段代码的核心思想是通过 DFS 遍历所有可能的字符大小写排列组合,并通过回溯来恢复现场,确保每个组合都是唯一的。通过剪枝优化,可以减少不必要的递归调用,提高代码效率。代码结构清晰,逻辑简单,适合处理这类排列组合问题。
九、526. 优美的排列 - 力扣(LeetCode)
算法代码:
class Solution {bool check[16];int ret;public:int countArrangement(int n) {dfs(1, n);return ret;}void dfs(int pos, int n) {if (pos == n + 1) {ret++;return;}for (int i = 1; i <= n; i++) {if (!check[i] && (pos % i == 0 || i % pos == 0)) {check[i] = true;dfs(pos + 1, n);check[i] = false; // 恢复现场}}}
};
这段代码的目的是计算给定整数 n
的所有优美排列的数量。优美排列的定义是:对于一个排列 nums
,如果对于每个位置 i
,nums[i]
满足 nums[i] % i == 0
或 i % nums[i] == 0
,那么这个排列就是优美的。例如,n = 2
,优美排列有 [1, 2]
和 [2, 1]
,输出 2
。
代码使用了深度优先搜索(DFS)和回溯的思想来遍历所有可能的排列,并通过剪枝优化减少不必要的递归调用。
代码思路解析
-
类的成员变量
-
check[16]
:一个布尔数组,用于记录数字1
到n
是否已经被使用。 -
ret
:用于记录优美排列的数量。
-
-
主函数
countArrangement
-
调用
dfs(1, n)
开始深度优先搜索,从位置1
开始。 -
返回结果
ret
。
-
-
DFS 函数
dfs
-
pos
:当前处理到的位置。 -
n
:排列的长度。 -
如果
pos == n + 1
,说明已经生成了一个完整的排列,且该排列是优美的,ret++
。 -
否则,遍历数字
1
到n
:-
如果数字
i
未被使用(!check[i]
),并且满足pos % i == 0
或i % pos == 0
,则选择该数字:-
将
check[i]
标记为true
,表示数字i
已被使用。 -
递归调用
dfs(pos + 1, n)
,处理下一个位置。 -
回溯时,将
check[i]
标记为false
,恢复现场。
-
-
-
代码优化建议
-
剪枝优化
-
在遍历数字
1
到n
时,可以通过条件pos % i == 0
或i % pos == 0
来限制选择范围,避免不必要的递归调用。
-
-
代码可读性
-
可以添加注释,解释每个步骤的作用,方便他人理解。
-
优化后的代码
class Solution {bool check[16]; // 记录数字是否被使用int ret; // 优美排列的数量public:int countArrangement(int n) {dfs(1, n); // 从位置 1 开始深度优先搜索return ret;}void dfs(int pos, int n) {if (pos == n + 1) { // 已经生成了一个完整的排列ret++; // 增加优美排列的数量return;}for (int i = 1; i <= n; i++) { // 遍历数字 1 到 nif (!check[i] && (pos % i == 0 || i % pos == 0)) { // 如果数字 i 未被使用且满足条件check[i] = true; // 选择数字 idfs(pos + 1, n); // 递归处理下一个位置check[i] = false; // 回溯,撤销选择}}}
};
代码细节解析
-
check
数组的作用-
check[i]
用于记录数字i
是否已经被使用,避免重复选择。
-
-
剪枝条件
-
pos % i == 0
或i % pos == 0
:确保选择的数字i
满足优美排列的条件。
-
-
回溯的恢复现场
-
在递归调用结束后,通过
check[i] = false
恢复现场,确保数字i
可以被其他排列使用。
-
总结
这段代码的核心思想是通过 DFS 遍历所有可能的排列,并通过剪枝优化减少不必要的递归调用。通过 check
数组记录数字的使用情况,并通过条件 pos % i == 0
或 i % pos == 0
确保排列是优美的。代码结构清晰,逻辑简单,适合处理这类排列问题。
十、 51. N 皇后 - 力扣(LeetCode)
算法代码:
class Solution {bool checkCol[10], checkDig1[20], checkDig2[20];vector<vector<string>> ret;vector<string> path;int n;public:vector<vector<string>> solveNQueens(int _n) {n = _n;path.resize(n);for (int i = 0; i < n; i++)path[i].append(n, '.');dfs(0);return ret;}void dfs(int row) {if (row == n) {ret.push_back(path);return;}for (int col = 0; col < n; col++) // 尝试在这⼀⾏放皇后{// 剪枝if (!checkCol[col] && !checkDig1[row - col + n] &&!checkDig2[row + col]) {path[row][col] = 'Q';checkCol[col] = checkDig1[row - col + n] =checkDig2[row + col] = true;dfs(row + 1);// 恢复现场path[row][col] = '.';checkCol[col] = checkDig1[row - col + n] =checkDig2[row + col] = false;}}}
};
这段代码的目的是解决 N 皇后问题,即在 n x n
的棋盘上放置 n
个皇后,使得它们互不攻击(即任意两个皇后不能在同一行、同一列或同一对角线上)。代码使用了深度优先搜索(DFS)和回溯的思想来遍历所有可能的放置方案。
代码思路解析
-
类的成员变量
-
checkCol[10]
:一个布尔数组,用于记录每一列是否已经被占用。 -
checkDig1[20]
:一个布尔数组,用于记录主对角线(从左上到右下)是否已经被占用。 -
checkDig2[20]
:一个布尔数组,用于记录副对角线(从右上到左下)是否已经被占用。 -
ret
:一个二维字符串向量,用于存储所有合法的棋盘布局。 -
path
:一个字符串向量,用于存储当前正在构建的棋盘布局。 -
n
:棋盘的大小(n x n
)。
-
-
主函数
solveNQueens
-
初始化
n
为输入的_n
。 -
初始化
path
为一个n x n
的棋盘,所有位置初始化为'.'
。 -
调用
dfs(0)
开始深度优先搜索,从第0
行开始。 -
返回结果
ret
。
-
-
DFS 函数
dfs
-
row
:当前处理到的行。 -
如果
row == n
,说明已经成功放置了n
个皇后,将当前棋盘布局path
加入ret
中。 -
否则,遍历当前行的每一列:
-
如果当前列
col
、主对角线row - col + n
和副对角线row + col
都没有被占用:-
在
path[row][col]
放置皇后'Q'
。 -
标记当前列、主对角线和副对角线为已占用。
-
递归调用
dfs(row + 1)
,处理下一行。 -
回溯时,移除刚刚放置的皇后,并恢复列、主对角线和副对角线的状态。
-
-
-
代码优化建议
-
剪枝优化
-
在遍历列时,通过
checkCol
、checkDig1
和checkDig2
数组快速判断当前位置是否可以放置皇后,避免不必要的递归调用。
-
-
代码可读性
-
可以添加注释,解释每个步骤的作用,方便他人理解。
-
优化后的代码
class Solution {bool checkCol[10]; // 记录列是否被占用bool checkDig1[20]; // 记录主对角线是否被占用bool checkDig2[20]; // 记录副对角线是否被占用vector<vector<string>> ret; // 所有合法的棋盘布局vector<string> path; // 当前棋盘布局int n; // 棋盘大小public:vector<vector<string>> solveNQueens(int _n) {n = _n;path.resize(n);for (int i = 0; i < n; i++) {path[i].append(n, '.'); // 初始化棋盘,所有位置为 '.'}dfs(0); // 从第 0 行开始深度优先搜索return ret;}void dfs(int row) {if (row == n) { // 已经成功放置了 n 个皇后ret.push_back(path); // 将当前棋盘布局加入结果集return;}for (int col = 0; col < n; col++) { // 遍历当前行的每一列// 检查当前列、主对角线和副对角线是否被占用if (!checkCol[col] && !checkDig1[row - col + n] && !checkDig2[row + col]) {path[row][col] = 'Q'; // 放置皇后checkCol[col] = checkDig1[row - col + n] = checkDig2[row + col] = true; // 标记占用dfs(row + 1); // 递归处理下一行// 回溯,恢复现场path[row][col] = '.';checkCol[col] = checkDig1[row - col + n] = checkDig2[row + col] = false;}}}
};
代码细节解析
-
checkCol
、checkDig1
、checkDig2
的作用-
checkCol[col]
:记录第col
列是否被占用。 -
checkDig1[row - col + n]
:记录主对角线是否被占用。row - col + n
是为了避免负数索引。 -
checkDig2[row + col]
:记录副对角线是否被占用。
-
-
放置皇后
-
在
path[row][col]
放置皇后'Q'
。 -
标记当前列、主对角线和副对角线为已占用。
-
-
回溯的恢复现场
-
移除刚刚放置的皇后
'Q'
,恢复为'.'
。 -
恢复列、主对角线和副对角线的状态。
-
总结
这段代码的核心思想是通过 DFS 遍历所有可能的皇后放置方案,并通过回溯来恢复现场,确保每个方案都是合法的。通过 checkCol
、checkDig1
和 checkDig2
数组快速判断当前位置是否可以放置皇后,避免不必要的递归调用。代码结构清晰,逻辑简单,适合解决 N 皇后问题。
十一、36. 有效的数独 - 力扣(LeetCode)
算法代码:
class Solution {bool row[9][10] = {false}; // 记录每一行数字是否出现bool col[9][10] = {false}; // 记录每一列数字是否出现bool grid[3][3][10] = {false}; // 记录每一个3x3小宫格数字是否出现public:bool isValidSudoku(vector<vector<char>>& board) {for (int i = 0; i < 9; i++) {for (int j = 0; j < 9; j++) {if (board[i][j] != '.') {int num = board[i][j] - '0';// 检查当前数字是否在当前行、列或小宫格中已经出现过if (row[i][num] || col[j][num] || grid[i / 3][j / 3][num]) {return false;}// 标记当前数字在当前行、列和小宫格中已经出现过row[i][num] = col[j][num] = grid[i / 3][j / 3][num] = true;}}}return true;}
};
这个代码的思路是判断一个9x9的数独棋盘是否有效。数独的有效性规则是:
-
每一行必须包含数字1-9,且不能重复。
-
每一列必须包含数字1-9,且不能重复。
-
每一个3x3的小宫格必须包含数字1-9,且不能重复。
代码思路解析
-
数据结构
-
row[9][10]
:用于记录每一行中数字1-9是否已经出现过。row[i][num]
表示第i
行中数字num
是否已经出现过。 -
col[9][10]
:用于记录每一列中数字1-9是否已经出现过。col[j][num]
表示第j
列中数字num
是否已经出现过。 -
grid[3][3][10]
:用于记录每一个3x3的小宫格中数字1-9是否已经出现过。grid[i/3][j/3][num]
表示第(i/3, j/3)
个小宫格中数字num
是否已经出现过。
-
-
遍历棋盘
-
使用双重循环遍历棋盘的每一个格子
(i, j)
。 -
如果当前格子不是空的(即
board[i][j] != '.'
),则提取出数字num = board[i][j] - '0'
。
-
-
检查有效性
-
检查当前数字
num
是否在当前行、当前列或当前3x3小宫格中已经出现过。如果出现过,则数独无效,返回false
。 -
如果没有出现过,则在
row
、col
和grid
中标记该数字已经出现过。
-
-
返回结果
-
如果遍历完整个棋盘都没有发现重复的数字,则数独有效,返回
true
。
-
代码优化建议
-
代码已经非常简洁高效,时间复杂度为O(1),因为数独棋盘的大小是固定的9x9。
-
代码的空间复杂度也是O(1),因为使用的辅助空间是固定的。
总结:
这个代码通过使用三个辅助数组来记录每一行、每一列和每一个3x3小宫格中数字的出现情况,从而高效地判断数独棋盘的有效性。代码逻辑清晰,实现简洁,适合解决数独有效性问题。
十二、 37. 解数独 - 力扣(LeetCode)
算法代码:
class Solution {bool row[9][10] = {false}; // 记录每一行数字是否出现bool col[9][10] = {false}; // 记录每一列数字是否出现bool grid[3][3][10] = {false}; // 记录每一个3x3小宫格数字是否出现public:void solveSudoku(vector<vector<char>>& board) {// 初始化for (int i = 0; i < 9; i++) {for (int j = 0; j < 9; j++) {if (board[i][j] != '.') {int num = board[i][j] - '0';row[i][num] = col[j][num] = grid[i / 3][j / 3][num] = true;}}}dfs(board);}bool dfs(vector<vector<char>>& board) {for (int i = 0; i < 9; i++) {for (int j = 0; j < 9; j++) {if (board[i][j] == '.') {// 尝试填入数字1-9for (int num = 1; num <= 9; num++) {if (!row[i][num] && !col[j][num] &&!grid[i / 3][j / 3][num]) {board[i][j] = '0' + num;row[i][num] = col[j][num] =grid[i / 3][j / 3][num] = true;if (dfs(board) == true) {return true; // 找到解,直接返回}// 回溯board[i][j] = '.';row[i][num] = col[j][num] =grid[i / 3][j / 3][num] = false;}}return false; // 所有数字都尝试过,无效}}}return true; // 所有格子都填满,找到解}
};
这个代码的思路是解决一个9x9的数独问题。数独的规则是:
-
每一行必须包含数字1-9,且不能重复。
-
每一列必须包含数字1-9,且不能重复。
-
每一个3x3的小宫格必须包含数字1-9,且不能重复。
代码思路解析
-
数据结构
-
row[9][10]
:用于记录每一行中数字1-9是否已经出现过。row[i][num]
表示第i
行中数字num
是否已经出现过。 -
col[9][10]
:用于记录每一列中数字1-9是否已经出现过。col[j][num]
表示第j
列中数字num
是否已经出现过。 -
grid[3][3][10]
:用于记录每一个3x3的小宫格中数字1-9是否已经出现过。grid[i/3][j/3][num]
表示第(i/3, j/3)
个小宫格中数字num
是否已经出现过。
-
-
初始化
-
在
solveSudoku
函数中,首先遍历整个棋盘,初始化row
、col
和grid
数组,记录已经填入的数字。
-
-
深度优先搜索(DFS)
-
dfs
函数通过递归尝试填充每一个空格子。 -
对于每一个空格子
(i, j)
,尝试填入数字1-9。 -
如果填入的数字
num
在当前行、当前列和当前3x3小宫格中都没有出现过,则填入该数字,并更新row
、col
和grid
数组。 -
递归调用
dfs
继续填充下一个空格子。 -
如果递归调用返回
true
,表示找到了一个有效的解,直接返回true
。 -
如果递归调用返回
false
,表示当前填入的数字num
无效,需要恢复现场(即回溯),尝试下一个数字。
-
-
回溯
-
如果所有数字1-9都尝试过,仍然没有找到有效的解,则返回
false
,表示当前路径无效,需要回溯到上一步。
-
-
终止条件
-
如果所有格子都填满,并且没有冲突,则返回
true
,表示找到了一个有效的解。
-
代码优化建议
-
代码已经非常简洁高效,通过回溯法系统地尝试所有可能的数字组合,直到找到一个有效的解。
-
代码的时间复杂度较高,因为需要尝试所有可能的组合,但在实际应用中,由于数独问题的约束条件较强,通常可以在合理的时间内找到解。
总结
这个代码通过深度优先搜索(DFS)和回溯法系统地尝试所有可能的数字组合,解决了数独问题。代码逻辑清晰,实现简洁,适合解决数独问题。通过递归和回溯,代码能够高效地找到数独的有效解。
十三、 79. 单词搜索 - 力扣(LeetCode)
算法代码:
class Solution {bool vis[7][7] = {false}; // 记录每个单元格是否被访问过int m, n; // 网格的行数和列数int dx[4] = {0, 0, -1, 1}; // 四个方向的x偏移量int dy[4] = {1, -1, 0, 0}; // 四个方向的y偏移量public:bool exist(vector<vector<char>>& board, string word) {m = board.size(), n = board[0].size();for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {if (board[i][j] == word[0]) {vis[i][j] = true; // 标记当前单元格为已访问if (dfs(board, i, j, word, 1)) {return true; // 找到匹配路径}vis[i][j] = false; // 回溯}}}return false; // 未找到匹配路径}bool dfs(vector<vector<char>>& board, int i, int j, string& word, int pos) {if (pos == word.size()) {return true; // 成功匹配整个单词}// 尝试四个方向for (int k = 0; k < 4; k++) {int x = i + dx[k], y = j + dy[k];if (x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] &&board[x][y] == word[pos]) {vis[x][y] = true; // 标记当前单元格为已访问if (dfs(board, x, y, word, pos + 1)) {return true; // 找到匹配路径}vis[x][y] = false; // 回溯}}return false; // 当前路径无效}
};
这个代码的思路是解决一个经典的单词搜索问题:在一个二维字符网格中,判断是否存在一条路径,使得路径上的字符按顺序连接起来恰好等于给定的单词。路径可以是水平或垂直相邻的单元格,且每个单元格只能使用一次。
代码思路解析
-
数据结构
-
vis[7][7]
:用于记录网格中的每个单元格是否已经被访问过。vis[i][j]
表示第i
行第j
列的单元格是否已经被访问。 -
m
和n
:分别表示网格的行数和列数。 -
dx[4]
和dy[4]
:用于表示四个方向(上、下、左、右)的偏移量。
-
-
主函数
exist
-
遍历整个网格,寻找与单词的第一个字符匹配的单元格。
-
如果找到匹配的单元格,则标记该单元格为已访问,并调用深度优先搜索(DFS)函数
dfs
继续匹配剩余的字符。 -
如果
dfs
返回true
,表示找到了匹配的路径,直接返回true
。 -
如果遍历完所有可能的起始单元格都没有找到匹配的路径,则返回
false
。
-
-
深度优先搜索(DFS)函数
dfs
-
如果当前匹配的位置
pos
等于单词的长度,说明已经成功匹配了整个单词,返回true
。 -
否则,尝试从当前单元格向四个方向(上、下、左、右)扩展,寻找下一个匹配的字符。
-
如果扩展的单元格在网格范围内、未被访问过,并且字符与单词的下一个字符匹配,则标记该单元格为已访问,并递归调用
dfs
。 -
如果递归调用返回
true
,表示找到了匹配的路径,直接返回true
。 -
如果递归调用返回
false
,表示当前路径无效,需要回溯(即恢复现场),尝试其他方向。
-
-
回溯
-
在递归调用返回
false
后,需要将当前单元格的访问状态恢复为未访问,以便其他路径可以尝试使用该单元格。
-
代码优化建议
-
代码已经非常简洁高效,通过深度优先搜索(DFS)和回溯法系统地尝试所有可能的路径。
-
代码的时间复杂度较高,因为需要尝试所有可能的路径,但在实际应用中,由于单词的长度和网格的大小有限,通常可以在合理的时间内找到解。
总结
这个代码通过深度优先搜索(DFS)和回溯法系统地尝试所有可能的路径,解决了单词搜索问题。代码逻辑清晰,实现简洁,适合解决类似的二维网格搜索问题。通过递归和回溯,代码能够高效地找到匹配的路径。
十四、 1219. 黄金矿工 - 力扣(LeetCode)
算法代码:
class Solution {bool vis[16][16] = {false}; // 记录每个单元格是否被访问过int dx[4] = {0, 0, 1, -1}; // 四个方向的x偏移量int dy[4] = {1, -1, 0, 0}; // 四个方向的y偏移量int m, n; // 网格的行数和列数int ret = 0; // 记录最大黄金总量public:int getMaximumGold(vector<vector<int>>& g) {m = g.size(), n = g[0].size();for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {if (g[i][j]) { // 如果当前单元格有黄金vis[i][j] = true; // 标记当前单元格为已访问dfs(g, i, j, g[i][j]); // 开始DFSvis[i][j] = false; // 回溯}}}return ret; // 返回最大黄金总量}void dfs(vector<vector<int>>& g, int i, int j, int path) {ret = max(ret, path); // 更新最大黄金总量for (int k = 0; k < 4; k++) { // 尝试四个方向int x = i + dx[k], y = j + dy[k];if (x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && g[x][y]) {vis[x][y] = true; // 标记当前单元格为已访问dfs(g, x, y, path + g[x][y]); // 递归DFSvis[x][y] = false; // 回溯}}}
};
这个代码的思路是解决一个黄金矿工问题:在一个二维网格中,每个单元格包含一定数量的黄金(用非负整数表示),矿工可以从任意一个非零单元格出发,向上下左右四个方向移动,收集黄金。矿工不能进入黄金数量为0的单元格,也不能重复访问同一个单元格。目标是找到一条路径,使得矿工收集的黄金总量最大。
代码思路解析
-
数据结构
-
vis[16][16]
:用于记录网格中的每个单元格是否已经被访问过。vis[i][j]
表示第i
行第j
列的单元格是否已经被访问。 -
dx[4]
和dy[4]
:用于表示四个方向(上、下、左、右)的偏移量。 -
m
和n
:分别表示网格的行数和列数。 -
ret
:用于记录当前找到的最大黄金总量。
-
-
主函数
getMaximumGold
-
遍历整个网格,寻找所有非零的单元格作为起点。
-
对于每一个非零的单元格,标记该单元格为已访问,并调用深度优先搜索(DFS)函数
dfs
开始收集黄金。 -
在
dfs
调用结束后,恢复该单元格的访问状态(回溯),以便其他路径可以尝试使用该单元格。 -
最终返回
ret
,即找到的最大黄金总量。
-
-
深度优先搜索(DFS)函数
dfs
-
更新当前路径的黄金总量
path
,并与ret
比较,更新ret
为较大值。 -
尝试从当前单元格向四个方向(上、下、左、右)扩展,寻找下一个可以收集黄金的单元格。
-
如果扩展的单元格在网格范围内、未被访问过,并且黄金数量不为0,则标记该单元格为已访问,并递归调用
dfs
。 -
在递归调用结束后,恢复该单元格的访问状态(回溯),以便其他路径可以尝试使用该单元格。
-
-
回溯
-
在递归调用结束后,需要将当前单元格的访问状态恢复为未访问,以便其他路径可以尝试使用该单元格。
-
代码优化建议
-
代码已经非常简洁高效,通过深度优先搜索(DFS)和回溯法系统地尝试所有可能的路径。
-
代码的时间复杂度较高,因为需要尝试所有可能的路径,但在实际应用中,由于网格的大小和黄金数量的限制,通常可以在合理的时间内找到解。
总结
这个代码通过深度优先搜索(DFS)和回溯法系统地尝试所有可能的路径,解决了黄金矿工问题。代码逻辑清晰,实现简洁,适合解决类似的二维网格搜索问题。通过递归和回溯,代码能够高效地找到收集黄金的最大路径。
十五、980. 不同路径 III - 力扣(LeetCode)
算法代码:
class Solution {bool vis[21][21] = {false}; // 记录每个单元格是否被访问过int dx[4] = {1, -1, 0, 0}; // 四个方向的x偏移量int dy[4] = {0, 0, 1, -1}; // 四个方向的y偏移量int ret = 0; // 记录满足条件的路径数量int m, n, step; // 网格的行数、列数和需要经过的总步数public:int uniquePathsIII(vector<vector<int>>& grid) {m = grid.size(), n = grid[0].size();int bx = 0, by = 0; // 起点的位置step = 0; // 初始化需要经过的步数for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {if (grid[i][j] == 0) {step++; // 统计空单元格的数量} else if (grid[i][j] == 1) {bx = i; // 记录起点的行by = j; // 记录起点的列}}}step += 2; // 包括起点和终点vis[bx][by] = true; // 标记起点为已访问dfs(grid, bx, by, 1); // 开始DFSreturn ret; // 返回满足条件的路径数量}void dfs(vector<vector<int>>& grid, int i, int j, int count) {if (grid[i][j] == 2) { // 如果当前单元格是终点if (count == step) { // 判断是否经过所有空单元格ret++; // 满足条件,路径数量加1}return;}for (int k = 0; k < 4; k++) { // 尝试四个方向int x = i + dx[k], y = j + dy[k];if (x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] &&grid[x][y] != -1) { // 检查是否合法vis[x][y] = true; // 标记当前单元格为已访问dfs(grid, x, y, count + 1); // 递归DFSvis[x][y] = false; // 回溯}}}
};
这个代码的思路是解决一个独特路径问题 III:在一个二维网格中,每个单元格可能包含以下值:
-
0
:表示空单元格,可以通过。 -
1
:表示起点。 -
2
:表示终点。 -
-1
:表示障碍物,不能通过。
目标是找到从起点到终点的所有路径,要求路径必须经过所有的空单元格(0
),并且每个单元格只能访问一次。
代码思路解析
-
数据结构
-
vis[21][21]
:用于记录网格中的每个单元格是否已经被访问过。vis[i][j]
表示第i
行第j
列的单元格是否已经被访问。 -
dx[4]
和dy[4]
:用于表示四个方向(上、下、左、右)的偏移量。 -
m
和n
:分别表示网格的行数和列数。 -
step
:表示需要经过的空单元格数量(包括起点和终点)。 -
ret
:用于记录满足条件的路径数量。
-
-
主函数
uniquePathsIII
-
遍历整个网格,统计空单元格的数量(
0
),并找到起点的位置(1
)。 -
将需要经过的总步数
step
设置为空单元格数量加2(包括起点和终点)。 -
标记起点为已访问,并调用深度优先搜索(DFS)函数
dfs
开始搜索路径。 -
最终返回
ret
,即满足条件的路径数量。
-
-
深度优先搜索(DFS)函数
dfs
-
如果当前单元格是终点(
2
),并且已经经过的步数count
等于step
,则说明找到了一条合法路径,ret
加1。 -
否则,尝试从当前单元格向四个方向(上、下、左、右)扩展,寻找下一个可以访问的单元格。
-
如果扩展的单元格在网格范围内、未被访问过,并且不是障碍物(
-1
),则标记该单元格为已访问,并递归调用dfs
。 -
在递归调用结束后,恢复该单元格的访问状态(回溯),以便其他路径可以尝试使用该单元格。
-
-
回溯
-
在递归调用结束后,需要将当前单元格的访问状态恢复为未访问,以便其他路径可以尝试使用该单元格。
-
代码优化建议
-
代码已经非常简洁高效,通过深度优先搜索(DFS)和回溯法系统地尝试所有可能的路径。
-
代码的时间复杂度较高,因为需要尝试所有可能的路径,但在实际应用中,由于网格的大小和路径的限制,通常可以在合理的时间内找到解。
总结
这个代码通过深度优先搜索(DFS)和回溯法系统地尝试所有可能的路径,解决了独特路径问题 III。代码逻辑清晰,实现简洁,适合解决类似的二维网格搜索问题。通过递归和回溯,代码能够高效地找到满足条件的路径数量。
相关文章:
综合练习 —— 递归、搜索与回溯算法
目录 一、1863. 找出所有子集的异或总和再求和 - 力扣(LeetCode) 算法代码: 代码思路 问题分析 核心思想 实现细节 代码解析 初始化 DFS 函数 时间复杂度 空间复杂度 示例运行 输入 运行过程 总结 二、 47. 全排列 II - 力扣&a…...
Python之使用动态导包优化软件加载速度
在开发大型 Python 软件时,可能会遇到以下问题:由于静态导入了大量模块,导致软件启动时间过长,用户体验不佳。例如,一个复杂的桌面应用程序或 Web 服务可能依赖于多个大型库(如 numpy、pandas、torch 或 Yolo),这些库在启动时被静态导入,即使某些功能模块在启动时并不…...
第16天:C++多线程完全指南 - 从基础到现代并发编程
第16天:C多线程完全指南 - 从基础到现代并发编程 一、多线程基础概念 1. 线程创建与管理(C11) #include <iostream> #include <thread>void hello() {std::cout << "Hello from thread " << std::this_…...
建筑兔零基础人工智能自学记录33|基础知识1
插入学习一下一些基础概念: 1、基本概念 人工智能:让机器像人一样思考。机器学习ML:计算机获取知识的过程。深度学习:机器的一种思考方式(借助神经网络)。 三者关系 2、机器学习的方式 监督学习&#x…...
win11编译pytorchaudio cuda128版本流程
1. 前置条件 本篇续接自 win11编译pytorch cuda128版本流程,阅读前请先参考上一篇配置环境。 访问https://kkgithub.com/pytorch/audio/archive/refs/tags/v2.6.0.tar.gz下载源码,下载后解压; 2. 编译 在visual studio 2022安装目录下查找…...
Python—Excel全字段转json文件(极速版+GUI界面打包)
目录 专栏导读1、背景介绍2、库的安装3、核心代码4、完整代码(简易版)5、进阶版(GUI)总结专栏导读 🌸 欢迎来到Python办公自动化专栏—Python处理办公问题,解放您的双手 🏳️🌈 博客主页:请点击——> 一晌小贪欢的博客主页求关注 👍 该系列文章专栏:请点击——…...
NLP学习记录十一:位置编码
目录 一、位置编码的意义 二、位置编码方法 三、代码实现 一、位置编码的意义 在标准的注意力机制中,每个查询都会关注所有的键-值对并生成一个注意力输出,模型并没有考虑到输入序列每个token的顺序关系。 以["我&qu…...
算法之算法主题
程序员数学 《程序员数学 v2.0》 | 小傅哥 bugstack 虫洞栈 智力题 头脑风暴题目 | Java 全栈知识体系...
【三维分割】LangSplat: 3D Language Gaussian Splatting(CVPR 2024 highlight)
论文:https://arxiv.org/pdf/2312.16084 代码:https://github.com/minghanqin/LangSplat 文章目录 一、3D language field二、回顾 Language Fields的挑战三、使用SAM学习层次结构语义四、Language Fields 的 3DGS五、开放词汇查询(Open-voca…...
Wireshark:自定义类型帧解析
文章目录 1. 前言2. 背景3. 开发 Lua 插件 1. 前言 限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。 2. 背景 Wireshark 不认识用 tcpdump 抓取的数据帧,仔细分析相关代码和数据帧后,…...
ES6 特性全面解析与应用实践
1、let let 关键字用来声明变量,使用let 声明的变量有几个特点: 1) 不允许重复声明 2) 块儿级作用域 3) 不存在变量提升 4) 不影响作用域链 5) 暂时性死区 6)不与顶级对象挂钩 在代码块内,使用let命令声明变量之前&#x…...
Qt跨线程信号槽调用:为什么信号不能像普通函数那样调用
1. 信号与槽机制的基本原理 在 Qt 中,信号与槽机制是一种事件驱动的通信方式,用于对象之间的解耦交互。其关键特点如下: 信号不能直接调用 信号只是一个声明,并没有实际的函数实现。它们通过 emit 关键字在对象内部被触发&…...
Zookeeper(79)如何进行Zookeeper的监控?
对 Zookeeper 进行监控是确保其高可用性和性能的关键步骤。监控 Zookeeper 通常包括以下几个方面: 健康检查:检查 Zookeeper 节点是否在线。性能指标:监控关键性能指标,如请求延迟、事务处理量等。日志监控:监控 Zook…...
【江科大STM32】TIM输出比较-PWM功能(学习笔记)
一、PWM驱动LED呼吸灯 接线图: PWM的初始化: 具体步骤: ①RCC开启时钟(把要用的TIM外设和GPIO外设时钟都打开) ② 配置时基单元,包括前面的时钟源选择 ③配置输出比较单元,里面包括CCR的值ÿ…...
playbin之autoplug_factories源码剖析
一、autoplug_factories_cb /* Called when we must provide a list of factories to plug to pad with caps.* We first check if we have a sink that can handle the format and if we do, we* return NULL, to expose the pad. If we have no sink (or the sink does not…...
Spring Cloud之注册中心之Nacos的使用
目录 Naacos 服务注册/服务发现 引⼊Spring Cloud Alibaba依赖 引入Nacos依赖 引入Load Balance依赖 配置Nacos地址 服务端调用 启动服务 Naacos Nacos是Spring Cloud Alibaba的组件, Spring Cloud Alibaba遵循Spring Cloud中定义的服务注册, 服务发现规范. 因此使⽤Na…...
React antd的datePicker自定义,封装成组件
一、antd的datePicker自定义 需求:用户需要为日期选择器的每个日期单元格添加一个Tooltip,当鼠标悬停时显示日期、可兑换流量余额和本公会可兑流量。这些数据需要从接口获取。我需要结合之前的代码,确保Tooltip正确显示,并且数据…...
【tplink】校园网接路由器如何单独登录自己的账号,wan-lan和lan-lan区别
老式路由器TPLINK,接入校园网后一人登录,所有人都能通过连接此路由器上网,无法解决遂上网搜索,无果,幸而偶然看到一个帖子说要把信号源网线接入路由器lan口,开启新世界。 一、wan-lan,lan-lan区…...
散户情绪周期模型(情绪影响操作)
目录 一、个股上涨阶段情绪演化二、个股下跌阶段情绪演化三、底部震荡阶段情绪演化四、情绪观察与操作工具箱1. 情绪自测量表(每日收盘后记录)2. 情绪-指标对照表 五、高阶情绪管理技巧1.认知重构训练2.生理指标监控(需配合智能手表ÿ…...
对比Grok3 普通账户与 30 美元 Super 账户:默认模式、Think 和 DeepSearch 次数限制以及如何升级
面对这个马斯克旗下的"最聪明"的人工智能,很多人都不知道他们的基本模式,本期将从几个方面开始说明: Grok3的背景与功能 账户类型及其详细背景 使用限制 使用限制对比表 如何充值使用 Super 账户 纯干货,带你了解…...
小程序Three Dof识别 实现景区AR体验
代码工程 GitCode - 全球开发者的开源社区,开源代码托管平台 dof...
主流Linux发行版优缺点整理及对比指南(文末附表格)
Linux发行版种类繁多,各有其设计理念和适用场景。本文整理常见发行版的优缺点,并附对比表格,帮助用户根据需求选择最适合的系统。 1. Ubuntu 定位:适合新手的通用型桌面/服务器系统优点: 安装简单,社区支持…...
用大白话解释搜索引擎Elasticsearch是什么,有什么用,怎么用
Elasticsearch是什么? Elasticsearch(简称ES)就像一个“超级智能的图书馆管理系统”,专门帮你从海量数据中快速找到想要的信息。它底层基于倒排索引技术(类似书籍的目录页),能秒级搜索和分析万…...
坐标变换及视图变换和透视变换(相机透视模型)
文章目录 2D transformationScaleReflectionShear(切变)Rotation around originTranslationReverse变换顺序复杂变换的分解 齐次坐标(Homogenous Coordinates)3D transformationScale&TranslationRotation Viewing / Camera t…...
C# 基于.NET Framework框架WPF应用程序-MQTTNet库实现MQTT消息订阅发布
C# 基于.NET Framework框架WPF应用程序-MQTTNet库实现MQTT消息订阅发布 MQTT简述MQTTNet简述创建项目(基于.NET Framework框架)安装MQTTNet库项目源码运行效果 MQTT简述 mqtt官网 MQTTNet简述 MQTTnet MQTTnet 是一个强大的开源 MQTT 客户端库&#…...
Python实现视频播放器
Python实现视频播放器 Python实现视频播放器,在如下博文中介绍过 Python实现本地视频/音频播放器https://blog.csdn.net/cnds123/article/details/137874107 Python简单GUI程序示例 中 “四、视频播放器” https://blog.csdn.net/cnds123/article/details/122903…...
介绍一款飞算JavaAI编程工具,集成到idea,图文并茂
飞算的插件下载地址,里边也有安装步骤: JavaAI 下载 从file-》setting-》plugin,然后走图中所示 选择从磁盘安装插件:找到下载好的压缩包然后进行idea重启 根据提示模块可以生成代码,就是需要等待,后期不…...
【大数据】Spark Executor内存分配原理与调优
【大数据】Spark Executor内存管理与调优 Executor内存总体布局 统一内存管理 堆内内存 (On-heap Memory) 堆外内存 (Off-heap Memory) Execution 内存和 Storage 内存动态占用机制 任务内存管理(Task Memory Manager) 只用了堆内内存的示例 用了…...
Python 课堂点名桌面小程序
一、场景分析 闲来无事,老婆说叫我开发一个课堂点名桌面小程序,给她在课堂随机点名学生问问题。 人生苦短,那就用 Python 给她写一个吧。 二、依赖安装 因为要用到 excel,所以安装两个依赖: pip install openpyxl…...
配置Spring Boot中的Jackson序列化
配置Spring Boot中的Jackson序列化 在开发基于Spring Boot的应用程序时,Jackson是默认的JSON序列化和反序列化工具。它提供了强大的功能,可以灵活地处理JSON数据。然而,Jackson的默认行为可能无法完全满足我们的需求。例如,日期格…...
Rust学习总结之-match
Rust 有一个叫做 match 的极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较,并根据相匹配的模式执行相应代码。模式可由字面量、变量、通配符和许多其他内容构成。 一:match定义 可以把 match 表达式想象成某种硬币分类器&a…...
实践教程:使用DeepSeek实现PDF转Word的高效方案
🎈Deepseek推荐工具 PDF文件因其跨平台、格式稳定的特性被广泛使用,但在内容编辑场景中,用户常需将PDF转换为可编辑的Word文档。传统的付费工具(如Adobe Acrobat)或在线转换平台存在成本高、隐私风险等问题。本文将使…...
鸿蒙 ArkUI 实现 2048 小游戏
2048 是一款经典的益智游戏,玩家通过滑动屏幕合并相同数字的方块,最终目标是合成数字 2048。本文基于鸿蒙 ArkUI 框架,详细解析其实现过程,帮助开发者理解如何利用声明式 UI 和状态管理构建此类游戏。 一、核心数据结构与状态管理…...
az devops login报错:Failed to authenticate using the supplied token.
PowerShell,az devops login报错: Failed to authenticate using the supplied token. 检查了一下PAT token是对的。 检查命令: az devops login --organization https://dev.azure.com/xxxxxxxx/ 乍一看好像没问题问题,然后想…...
C ++ 静态存储区+堆空间
静态存储区 特点: 1:生命周期很长,main函数开始之前就存在,main函数结束,才结束 2:同名变量的管理,与栈不一样(重名变量前提,作用域一样): 栈:遇到重名变…...
gtest 和 gmock讲解
Google Test(gtest)和 Google Mock(gmock)是 Google 开发的用于 C 的测试框架和模拟框架,以下是对它们的详细讲解: Google Test(gtest) 简介 Google Test 是一个用于 C 的单元测试框…...
docker的下载与使用(一)
本文默认使用linux系统以及会linux的基本指令,windows下安装docker较为繁琐 docker是什么 Docker 是一个开源的应用容器引擎,基于go 语言并遵从 Apache2.0 协议开源。 Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&…...
鸿蒙HarmonyOS NEXT开发:组件-样式-基础 2
// 1 // 2 ArkUI 基本语法 // 方舟开发框架(简称:ArkUI),是一套 构建HarmonyOS应用 界面 的框架。 // 构建页面的最小单位就是 "组件"。 // 组件名(参数) { // 内容 // } // .属性1() // .属性2() // .属性N() import text from @ohos.graphics.text // @En…...
如何理解数据库的几种事务隔离级别?以及如何理解脏读、不可重复度、幻读?
在多用户并发访问数据库时,数据库系统需要通过事务隔离级别来控制不同事务之间的相互影响。不同的隔离级别可以避免或减少在并发环境下可能出现的数据不一致或冲突。常见的事务隔离级别有四种:读未提交(Read Uncommitted)、读已提…...
计算机网络基础简答题资料(对口高考)
1、什么是计算机网络?计算机网络的功能有哪些? 答案:计算机网络,是指将分布在不同地理位置、具有独立功能的多台计算机及其外围设备,通过通信设备和通信线路连接起来,在网络操作系统、网络管理软件及网络通…...
在docker容器中运行vllm部署deepseek-r1大模型
# 在本地部署python环境 cd /app/ python -m venv myenv # 激活虚拟环境 source /app/myenv/activate # 要撤销激活一个虚拟环境,请输入: deactivate# 进入虚拟环境安装modelscope pip install modelscope# 下载大模型(7B为例) modelscope do…...
HeidiSQL如何替换代码中的某些信息
1.SQL代码里的某些内容,比如2025年这个日期信息,我想替换成2024年的,按照:“搜索”---“替换文本”然后按照图片上的步骤来就可以了,特别是在sql代码有几百行甚至几千行的时候使用 2.SQL代码的表格对象中的数据如何一次性把某个内…...
Wireshark 插件开发实战指南
Wireshark 插件开发实战指南 环境搭建流程图 #mermaid-svg-XpNibno7BIyfzNn5 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-XpNibno7BIyfzNn5 .error-icon{fill:#552222;}#mermaid-svg-XpNibno7BIyfzNn5 .error-t…...
【大模型➕知识图谱】大模型结合医疗知识图谱:解锁智能辅助诊疗系统新范式
【大模型➕知识图谱】大模型结合医疗知识图谱:解锁智能辅助诊疗系统新范式 大模型结合医疗知识图谱:解锁智能辅助诊疗系统新范式引言一、系统架构1.1 系统架构图1.2 架构模块说明1.2.1 用户输入1.2.2 大模型(语义理解与意图识别)1.2.3 Agent(问题解析与任务分配)1.2.4 问…...
大模型应用落地具体规划方案
摘要 本篇文章主要探讨大模型应用落地的具体规划方案,包含六点内容的分享,分别是: 大模型本地部署架构 大模型应用交互场景 基于阿里云RAG 项目的实现方案 大模型推荐落地场景方案 大模型应用落地发展规划 大模型开源架构选型推荐 在阅…...
【Qt】MVC设计模式
目录 一、搭建MVC框架 二、创建数据库连接单例类SingleDB 三、数据库业务操作类model设计 四、control层,关于model管理类设计 五、view层即为窗口UI类 一、搭建MVC框架 里面的bin、lib、database文件夹以及sqlite3.h与工程后缀为.pro文件的配置与上次发的文章…...
python量化交易——金融数据管理最佳实践——qteasy创建本地数据源
文章目录 qteasy金融历史数据管理总体介绍本地数据源——DataSource对象默认数据源查看数据表查看数据源的整体信息最重要的数据表其他的数据表 从数据表中获取数据向数据表中添加数据删除数据表 —— 请尽量小心,删除后无法恢复!!总结 qteas…...
深入探索Python机器学习算法:监督学习(线性回归,逻辑回归,决策树与随机森林,支持向量机,K近邻算法)
文章目录 深入探索Python机器学习算法:监督学习一、线性回归二、逻辑回归三、决策树与随机森林四、支持向量机五、K近邻算法 深入探索Python机器学习算法:监督学习 在机器学习领域,Python凭借其丰富的库和简洁的语法成为了众多数据科学家和机…...
word转换为pdf后图片失真解决办法、高质量PDF转换方法
1、安装Adobe Acrobat Pro DC 自行安装 2、配置Acrobat PDFMaker (1)点击word选项卡上的Acrobat插件,(2)点击“首选项”按钮,(3)点击“高级配置”按钮(4)点…...
【MATLAB例程】三维下的IMM(交互式多模型),模型使用CV(匀速)和CA(匀加速)
给出三维下的交互式多模型(IMM)matlab例程,模型使用匀速运动CV和匀加速运动CA,滤波使用EKF(扩展卡尔曼滤波) 文章目录 代码运行结果程序结构 代码讲解模型定义:轨迹生成:IMM核心流程…...