代码随想录——回溯
文章目录
- 组合
- 组合总数
- 电话号码的字母组合
- 组合总数
- 组合总数Ⅱ
- 分割回文串
- 复原IP地址
- 子集
- 子集Ⅱ
- 非递减子序列
- 去重的实现方法
- 方法 1:**排序 + 跳过重复元素**
- 方法 2:**使用哈希表或数组记录已使用的数字**
- 去重的完整示例
- 总结
- 本题代码
- 全排列
- 全排列Ⅱ
- 重新安排行程
- 题目描述
- 思路
- 代码逻辑
- N皇后
- 解数独
组合
77. 组合
思路:回溯
- 从
1
开始,逐个尝试选择数字,结果是升序序列。 - 如果当前组合的长度等于
k
,将其加入结果。 - 如果未达到
k
,继续递归选择下一个数字。 - 通过回溯(撤销选择)来尝试其他可能性。
/*** Return an array of arrays of size *returnSize.* The sizes of the arrays are returned as *returnColumnSizes array.* Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().*/// 回溯函数
void backtrack(int n, int k, int start, int *path, int pathSize, int **result, int *resultSize, int *returnColumnSizes) {// 如果当前组合的长度等于 k,将其加入结果if (pathSize == k) {result[*resultSize] = malloc(sizeof(int) * k); // 为当前组合分配内存memcpy(result[*resultSize], path, sizeof(int) * k); // 复制当前组合到结果数组returnColumnSizes[*resultSize] = k; // 设置当前组合的大小(*resultSize)++; // 结果数组的索引加 1return;}//剪枝,也可以不加if(n-start+1+pathSize<k)return;// 从 start 开始尝试选择数字for (int i = start; i <= n; i++) {path[pathSize] = i; // 选择当前数字backtrack(n, k, i + 1, path, pathSize + 1, result, resultSize, returnColumnSizes); // 递归选择下一个数字}
}// 主函数:生成所有组合
int** combine(int n, int k, int* returnSize, int** returnColumnSizes) {// 计算组合的总数 C(n, k)int total = 1;for (int i = 1; i <= k; i++) {total *= (n - i + 1);total /= i;}*returnSize = total; // 设置返回的组合总数// 分配内存int **result = malloc(sizeof(int*) * total); // 结果数组*returnColumnSizes = malloc(sizeof(int) * total); // 每个组合的大小数组int *path = malloc(sizeof(int) * k); // 当前组合的临时数组int resultSize = 0; // 结果数组的当前索引backtrack(n, k, 1, path, 0, result, &resultSize, *returnColumnSizes); // 调用回溯函数生成所有组合// 释放临时数组的内存free(path);return result; // 返回结果数组
}
组合总数
216. 组合总和 III
思路:回溯
- 问题分解:
- 从
1
到9
中选择k
个数,使得它们的和等于n
。 - 每个数字只能使用一次,且组合中的数字按升序排列。
- 从
- 回溯算法的关键点:
- 选择数字:从
1
开始,逐个尝试选择数字。 - 递归:在选择一个数字后,递归选择下一个数字。
- 剪枝:如果当前组合的和已经超过
n
,或者组合的长度已经超过k
,则停止递归(剪枝)。 - 保存结果:如果当前组合的长度等于
k
且和等于n
,将其加入结果。
- 选择数字:从
- 实现步骤:
- 使用一个临时数组
path
存储当前组合。 - 使用递归函数
dfs
遍历所有可能的组合。 - 在递归过程中,确保组合中的数字按升序排列,避免重复组合。
- 使用一个临时数组
/*** Return an array of arrays of size *returnSize.* The sizes of the arrays are returned as *returnColumnSizes array.* Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().*/// 深度优先搜索(DFS)函数
void dfs(int k, int n, int start, int *returnSize, int *path, int pathsize, int **res, int **returnColumnSizes) {// 如果当前组合的长度等于 k 且和为 n,将其加入结果数组if (n == 0 && pathsize == k) {res[*returnSize] = malloc(sizeof(int) * k); // 为当前组合分配内存memcpy(res[*returnSize], path, sizeof(int) * k); // 复制当前组合到结果数组(*returnColumnSizes)[*returnSize] = k; // 设置当前组合的大小(*returnSize)++; // 结果数组的索引加 1return;}// 如果当前和已经小于 0 或组合长度超过 k,直接返回if (n < 0 || pathsize >= k) return;// 从 start 开始尝试选择数字for (int i = start; i <= 9; i++) {path[pathsize] = i; // 选择当前数字dfs(k, n - i, i + 1, returnSize, path, pathsize + 1, res, returnColumnSizes); // 递归选择下一个数字}
}// 主函数:生成所有符合条件的组合
int** combinationSum3(int k, int n, int* returnSize, int** returnColumnSizes) {*returnSize = 0; // 初始化返回的组合数量为 0// 分配内存*returnColumnSizes = malloc(sizeof(int) * 512); // 每个组合的大小数组,最多有 2^9=512 个组合int *path = malloc(sizeof(int) * k); // 当前组合的临时数组int **res = malloc(sizeof(int*) * 512); // 结果数组,最多有 2^9=512 个组合// 调用 DFS 函数生成所有组合dfs(k, n, 1, returnSize, path, 0, res, returnColumnSizes);// 释放临时数组的内存free(path);return res; // 返回结果数组
}
电话号码的字母组合
17. 电话号码的字母组合
思路:思路不难,就是用字母替换相应的数字
// 数字到字母的映射
const char *digitToLetters[10] = {"", // 0"", // 1"abc", // 2"def", // 3"ghi", // 4"jkl", // 5"mno", // 6"pqrs", // 7"tuv", // 8"wxyz" // 9
};// 深度优先搜索(DFS)函数
void dfs(char *digits, int *returnSize, char *path, int pathsize, char **res) {// 如果当前组合的长度等于 digits 的长度,将其加入结果数组if (pathsize == strlen(digits)) {res[*returnSize] = malloc(sizeof(char) * (pathsize + 1)); // 为当前组合分配内存memcpy(res[*returnSize], path, sizeof(char) * pathsize); // 复制当前组合到结果数组res[*returnSize][pathsize] = '\0'; // 添加字符串结束符(*returnSize)++; // 结果数组的索引加 1return;}// 获取当前数字对应的字母集合int digit = digits[pathsize] - '0'; // 将字符转换为数字const char *letters = digitToLetters[digit]; // 获取对应的字母集合// 遍历当前数字对应的所有字母for (int i = 0; i < strlen(letters); i++) {path[pathsize] = letters[i]; // 选择当前字母dfs(digits, returnSize, path, pathsize + 1, res); // 递归选择下一个字母}
}// 主函数:生成所有字母组合
char** letterCombinations(char* digits, int* returnSize) {int len = strlen(digits);*returnSize = 0; // 初始化返回的组合数量为 0// 如果输入为空,直接返回空数组if (len == 0) {return NULL;}// 计算所有可能的组合总数int total = 1;for (int i = 0; i < len; i++) {int digit = digits[i] - '0';total *= strlen(digitToLetters[digit]);}// 分配内存char **res = malloc(sizeof(char*) * total); // 结果数组char *path = malloc(sizeof(char) * (len + 1)); // 当前组合的临时数组// 调用 DFS 函数生成所有组合dfs(digits, returnSize, path, 0, res);// 释放临时数组的内存free(path);return res; // 返回结果数组
}
组合总数
39. 组合总和
思路:与前面组合总数
思路差不多,不同点在于本题的数字可以重复选取
/*** Return an array of arrays of size *returnSize.* The sizes of the arrays are returned as *returnColumnSizes array.* Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().*/
/*** 递归函数:深度优先搜索(DFS)寻找所有组合* @param candidates 候选数字数组* @param n 候选数组的大小* @param start 当前递归的起始索引(避免重复组合)* @param target 剩余目标和* @param path 当前路径(存储当前组合)* @param pathsize 当前路径的大小* @param res 结果数组(存储所有有效组合)* @param returnSize 当前找到的有效组合数量* @param returnColumnSizes 存储每个组合大小的数组*/
void dfs(int *candidates, int n, int start, int target, int *path, int pathsize, int **res, int *returnSize, int **returnColumnSizes) {// 如果剩余目标和为 0,说明当前路径是一个有效组合if (target == 0) {// 分配内存存储当前组合res[*returnSize] = malloc(sizeof(int) * pathsize);// 将当前路径复制到结果数组中memcpy(res[*returnSize], path, sizeof(int) * pathsize);// 记录当前组合的大小(*returnColumnSizes)[*returnSize] = pathsize;// 有效组合数量加 1(*returnSize)++;return;}// 如果剩余目标和为负数,说明当前路径无效,直接返回if (target < 0) {return;}// 遍历候选数组,尝试将每个数字加入路径for (int i = start; i < n; i++) {// 将当前候选数字加入路径path[pathsize] = candidates[i];// 递归调用,更新剩余目标和及路径大小dfs(candidates, n, i, target - candidates[i], path, pathsize + 1, res, returnSize, returnColumnSizes);}return;
}/*** 主函数:寻找所有组合* @param candidates 候选数字数组* @param candidatesSize 候选数组的大小* @param target 目标和* @param returnSize 返回有效组合的数量* @param returnColumnSizes 返回每个组合的大小* @return 返回所有有效组合的数组*/
int** combinationSum(int* candidates, int candidatesSize, int target, int* returnSize, int** returnColumnSizes) {// 初始化有效组合数量为 0*returnSize = 0;// 为存储组合大小的数组分配内存(假设最多有 150 种组合)*returnColumnSizes = malloc(sizeof(int) * 150);// 为当前路径分配内存(假设路径大小最多为 30)int *path = malloc(sizeof(int) * 30);// 为结果数组分配内存(假设最多有 150 种组合)int **res = malloc(sizeof(int*) * 150);// 调用 DFS 函数,开始寻找组合dfs(candidates, candidatesSize, 0, target, path, 0, res, returnSize, returnColumnSizes);// 释放临时路径数组的内存free(path);// 返回结果数组return res;
}
组合总数Ⅱ
40. 组合总和 II
思路:
- 排序:
- 对
candidates
排序,方便去重和剪枝。
- 对
- 回溯算法:
- 通过递归遍历所有可能的组合。
- 在每一层递归中,从当前索引开始遍历候选数组。
- 去重:
- 如果当前数字与前一个数字相同,且不是当前递归层的第一个数字,则跳过。
- 剪枝:
- 如果当前数字大于剩余目标和,直接跳过后续数字。
- 终止条件:
- 如果剩余目标和为 0,保存当前组合。
- 如果剩余目标和为负数,直接返回。
/*** Return an array of arrays of size *returnSize.* The sizes of the arrays are returned as *returnColumnSizes array.* Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().*/
/*** 递归函数:深度优先搜索(DFS)寻找所有组合* @param candidates 候选数字数组* @param n 候选数组的大小* @param start 当前递归的起始索引(避免重复组合)* @param target 剩余目标和* @param path 当前路径(存储当前组合)* @param pathsize 当前路径的大小* @param res 结果数组(存储所有有效组合)* @param returnSize 当前找到的有效组合数量* @param returnColumnSizes 存储每个组合大小的数组*/
void dfs(int *candidates, int n, int start, int target, int *path, int pathsize, int **res, int *returnSize, int **returnColumnSizes) {// 如果剩余目标和为 0,说明当前路径是一个有效组合if (target == 0) {// 分配内存存储当前组合res[*returnSize] = malloc(sizeof(int) * pathsize);// 将当前路径复制到结果数组中memcpy(res[*returnSize], path, sizeof(int) * pathsize);// 记录当前组合的大小(*returnColumnSizes)[*returnSize] = pathsize;// 有效组合数量加 1(*returnSize)++;return;}// 如果剩余目标和为负数,说明当前路径无效,直接返回if (target < 0) {return;}// 遍历候选数组,尝试将每个数字加入路径for (int i = start; i < n; i++) {// 如果当前数字与前一个数字相同,且不是递归的起始位置,跳过(去重)if(i>start&&candidates[i-1]==candidates[i])continue;// 将当前候选数字加入路径path[pathsize] = candidates[i];// 递归调用,更新剩余目标和及路径大小dfs(candidates, n, i+1, target - candidates[i], path, pathsize + 1, res, returnSize, returnColumnSizes);}return;
}int cmp(const void *a, const void *b) {return *(int *)a - *(int *)b;
}
/*** 主函数:寻找所有组合* @param candidates 候选数字数组* @param candidatesSize 候选数组的大小* @param target 目标和* @param returnSize 返回有效组合的数量* @param returnColumnSizes 返回每个组合的大小* @return 返回所有有效组合的数组*/
int** combinationSum2(int* candidates, int candidatesSize, int target, int* returnSize, int** returnColumnSizes) {// 初始化有效组合数量为 0*returnSize = 0;// 为存储组合大小的数组分配内存(假设最多有 150 种组合)*returnColumnSizes = malloc(sizeof(int) * 150);// 为当前路径分配内存(假设路径大小最多为 30)int *path = malloc(sizeof(int) * 30);// 为结果数组分配内存(假设最多有 150 种组合)int **res = malloc(sizeof(int*) * 150);qsort(candidates,candidatesSize,sizeof(int),cmp);// 调用 DFS 函数,开始寻找组合dfs(candidates, candidatesSize, 0, target, path, 0, res, returnSize, returnColumnSizes);// 释放临时路径数组的内存free(path);// 返回结果数组return res;
}
分割回文串
131. 分割回文串
思路:dfs+回溯
- 使用回溯算法遍历所有可能的分割方式。
- 在每一层递归中,尝试从当前位置开始分割出一个回文子串。
- 如果当前子串是回文,则将其加入路径,并递归处理剩余部分。
- 如果当前分割位置到达字符串末尾,说明找到一种有效的分割方案,将其保存到结果中。
// 判断子串 s[left...right] 是否是回文
bool ishuiwen(char *s, int left, int right) {while (left < right) {if (s[left] != s[right]) return false; // 如果字符不相等,不是回文left++;right--;}return true; // 是回文
}// 回溯函数
void dfs(char *s, int start, int len, char **path, int pathSize, char ***res, int *returnSize, int **returnColumnSizes) {// 如果当前分割位置到达字符串末尾,保存当前分割方案if (start == len) {res[*returnSize] = malloc(sizeof(char*) * pathSize); // 分配内存存储当前分割方案for (int i = 0; i < pathSize; i++) {res[*returnSize][i] = malloc(strlen(path[i]) + 1); // 分配内存并复制字符串strcpy(res[*returnSize][i], path[i]);}(*returnColumnSizes)[*returnSize] = pathSize; // 记录当前分割方案的大小(*returnSize)++; // 有效分割方案数量加 1return;}// 尝试从当前位置开始分割for (int i = start; i < len; i++) {// 如果子串 s[start...i] 是回文,则继续递归if (ishuiwen(s, start, i)) {// 将当前回文子串加入路径path[pathSize] = malloc(i - start + 2); // 分配内存并复制字符串strncpy(path[pathSize], s + start, i - start + 1);path[pathSize][i - start + 1] = '\0'; // 添加字符串结束符// 递归处理剩余部分dfs(s, i + 1, len, path, pathSize + 1, res, returnSize, returnColumnSizes);// 回溯,移除当前子串free(path[pathSize]);}}
}// 主函数
char ***partition(char *s, int *returnSize, int **returnColumnSizes) {int len = strlen(s);// 初始化结果*returnSize = 0; // 有效分割方案数量初始化为 0*returnColumnSizes = malloc(sizeof(int) * 100000); // 假设最多有 100000 种分割方案char ***res = malloc(sizeof(char**) * 100000); // 假设最多有 100000 种分割方案char **path = malloc(sizeof(char*) * len); // 当前路径// 调用回溯函数dfs(s, 0, len, path, 0, res, returnSize, returnColumnSizes);// 释放临时路径数组free(path);return res; // 返回所有有效的分割方案
}
复原IP地址
93. 复原 IP 地址
思路:dfs,回溯,和前面都差不多。
回溯算法
- 使用回溯算法尝试所有可能的分割方案。
- 每次尝试将字符串分割成 4 个部分,每个部分必须满足 IP 段的条件。
有效 IP 段的判断
- 每个 IP 段必须满足以下条件:
- 值在
0
到255
之间。 - 不能有前导零(除非是
0
本身)。 - 必须是数字字符。
- 值在
递归终止条件
- 如果已经分成 4 段且字符串被完全使用,当前分割方案是一个有效的 IP 地址,将其保存到结果中。
剪枝优化
- 如果当前分割的段数已经超过 4 段,直接返回。
- 如果剩余字符串的长度不足以分成剩余部分,直接返回。
/*** Note: The returned array must be malloced, assume caller calls free().*/// 判断子串 s[left...right] 是否是有效的 IP 段
bool isValidSegment(char *s, int left, int right) {if (right == left) return true; // 单个字符是有效的if (left > right || s[left] == '0') return false; // 前导零无效(除非是 "0" 本身)int sum = 0;for (int i = left; i <= right; i++) {if (s[i] < '0' || s[i] > '9') return false; // 非数字字符无效sum = sum * 10 + (s[i] - '0'); // 计算子串的数值if (sum > 255) return false; // 超过 255 无效}return true; // 子串有效
}// 回溯函数
void dfs(char *s, int start, int len, char **path, int pathSize, char **res, int *returnSize) {// 如果已经分成 4 段且字符串被完全使用if (pathSize == 4 && start == len) {// 分配内存存储当前 IP 地址res[*returnSize] = malloc(sizeof(char) * (len + 4)); // len + 4 是为了容纳 3 个点号和结束符int index = 0;// 将每一段拼接到结果中for (int i = 0; i < 4; i++) {strcpy(res[*returnSize] + index, path[i]); // 复制当前段index += strlen(path[i]); // 移动索引if (i < 3) {res[*returnSize][index++] = '.'; // 添加点号}}res[(*returnSize)++][index] = '\0'; // 添加字符串结束符,并增加有效 IP 地址数量return;}// 如果已经分成 4 段但字符串未完全使用,直接返回if (pathSize == 4) return;// 尝试从当前位置开始分割for (int i = start; i < len && i < start + 3; i++) { // 每段最多 3 位if (isValidSegment(s, start, i)) { // 如果子串 s[start...i] 是有效的 IP 段// 分配内存并复制当前段path[pathSize] = malloc(sizeof(char) * (i - start + 2)); // i - start + 1 是子串长度,+1 用于结束符strncpy(path[pathSize], s + start, i - start + 1); // 复制子串path[pathSize][i - start + 1] = '\0'; // 添加字符串结束符// 递归处理剩余部分dfs(s, i + 1, len, path, pathSize + 1, res, returnSize);// 回溯,释放当前段内存free(path[pathSize]);}}
}// 主函数
char **restoreIpAddresses(char *s, int *returnSize) {int len = strlen(s);*returnSize = 0; // 初始化有效 IP 地址数量为 0char **res = malloc(sizeof(char*) * 1000); // 假设最多有 1000 种有效 IP 地址char *path[4]; // 当前路径,存储 4 段 IP 地址// 调用回溯函数dfs(s, 0, len, path, 0, res, returnSize);return res; // 返回所有有效的 IP 地址
}
子集
78. 子集
思路:dfs+回溯
与上面几题的区别在于:本题每次递归的状态(path)都是我们需要的答案,因此没有if(path==numsSize)
这个终止条件,其他都一样。
具体见代码:
/*** Return an array of arrays of size *returnSize.* The sizes of the arrays are returned as *returnColumnSizes array.* Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().*/// 回溯函数
void dfs(int *nums, int numsSize, int start, int *path, int pathSize, int **res, int *returnSize, int **returnColumnSizes) {// 将当前路径保存到结果中res[*returnSize] = malloc(sizeof(int) * pathSize); // 分配内存存储当前子集memcpy(res[*returnSize], path, sizeof(int) * pathSize); // 复制当前路径到结果中(*returnColumnSizes)[*returnSize] = pathSize; // 记录当前子集的大小(*returnSize)++; // 增加结果的数量// 遍历数组,尝试选择或不选择当前元素for (int i = start; i < numsSize; i++) {// 选择当前元素path[pathSize] = nums[i]; // 将当前元素加入路径// 递归处理剩余元素dfs(nums, numsSize, i + 1, path, pathSize + 1, res, returnSize, returnColumnSizes);// 注意:这里不需要显式回溯,因为 pathSize 在递归调用时会自动恢复}return;
}// 主函数
int** subsets(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) {// 初始化结果数组int **res = malloc(sizeof(int*) * 1024); // 假设最多有 1024 个子集int *path = malloc(sizeof(int) * 10); // 假设路径长度最多为 10*returnSize = 0; // 初始化结果数量为 0*returnColumnSizes = malloc(sizeof(int) * 1024); // 假设最多有 1024 个子集// 调用回溯函数dfs(nums, numsSize, 0, path, 0, res, returnSize, returnColumnSizes);// 返回所有子集return res;
}
子集Ⅱ
90. 子集 II
思路:如果上题**78. 子集懂了,那么这题只需要注意去重就行,而去重的方法在40. 组合总和 II**也已经实现了。基于上题上题的基础,只需要加上排序和去重就可以了。
- 排序:
- 使用
qsort
对输入数组nums
进行排序,以便跳过重复元素。
- 使用
- 回溯函数
dfs
:- 将当前路径
path
保存到结果res
中。 - 遍历数组,选择或不选择当前元素。
- 如果当前元素与前一个元素相同且不是起始位置,则跳过(避免重复子集)。
- 将当前路径
/*** Return an array of arrays of size *returnSize.* The sizes of the arrays are returned as *returnColumnSizes array.* Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().*/
// 回溯函数
void dfs(int *nums, int numsSize, int start, int *path, int pathSize, int **res, int *returnSize, int **returnColumnSizes) {// 将当前路径保存到结果中res[*returnSize] = malloc(sizeof(int) * pathSize); // 分配内存存储当前子集memcpy(res[*returnSize], path, sizeof(int) * pathSize); // 复制当前路径到结果中(*returnColumnSizes)[*returnSize] = pathSize; // 记录当前子集的大小(*returnSize)++; // 增加结果的数量// 遍历数组,尝试选择或不选择当前元素for (int i = start; i < numsSize; i++) {if(i>start&&nums[i]==nums[i-1])continue;// 选择当前元素path[pathSize] = nums[i]; // 将当前元素加入路径// 递归处理剩余元素dfs(nums, numsSize, i + 1, path, pathSize + 1, res, returnSize, returnColumnSizes);// 注意:这里不需要显式回溯,因为 pathSize 在递归调用时会自动恢复}return;
}
int cmp(const void* a,const void* b){return *(int*)a-*(int*)b;
}
int** subsetsWithDup(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) {qsort(nums,numsSize,sizeof(int),cmp);// 初始化结果数组int **res = malloc(sizeof(int*) * 1024); // 假设最多有 1024 个子集int *path = malloc(sizeof(int) * 10); // 假设路径长度最多为 10*returnSize = 0; // 初始化结果数量为 0*returnColumnSizes = malloc(sizeof(int) * 1024); // 假设最多有 1024 个子集// 调用回溯函数dfs(nums, numsSize, 0, path, 0, res, returnSize, returnColumnSizes);// 返回所有子集return res;
}
非递减子序列
491. 非递减子序列
思路:dfs+回溯获取所有非递减子序列,选取pathSize>=2
的序列。
这里与之前不同的点在于去重的逻辑,前面的去重是在排序之后的数组进行,而本题,而本题并不能排序。采取的是用哈希表记录某数是否已经使用。
下面是AI生成的去重总结,我觉得不错:
去重的核心思想是:在同一递归层级中,如果当前数字已经使用过,则跳过该数字。
去重的实现方法
方法 1:排序 + 跳过重复元素
- 如果输入数组是有序的,可以直接跳过重复元素。
- 例如,在
nums = [4, 6, 7, 7]
中,当遍历到第二个7
时,如果发现它和前一个元素相同,则跳过。
代码示例:
if (i > start && nums[i] == nums[i - 1]) continue;
- 作用:
- 如果当前数字
nums[i]
和前一个数字nums[i - 1]
相同,并且i > start
,则跳过该数字。 - 这样可以避免在同一层级中重复使用相同的数字。
- 如果当前数字
- 局限性:
- 这种去重方法依赖于输入数组的有序性。如果输入数组是无序的,这种逻辑可能无法完全去重。
方法 2:使用哈希表或数组记录已使用的数字
- 在每一层递归中,使用一个哈希表或数组记录当前层级已经使用过的数字。
- 如果当前数字已经存在于哈希表中,则跳过该数字。
代码:
为了更通用地处理去重问题,可以在每一层递归中使用一个数组(或哈希表)记录已经使用过的数字。以下是改进的去重逻辑:
int used[201] = {0}; // 用于去重,记录当前递归层中已经使用过的数字
for (int i = start; i < numsSize; i++) {// 如果当前数字已经使用过,则跳过if (used[nums[i] + 100]) {continue;}used[nums[i] + 100] = 1; // 标记当前数字已使用// 其他逻辑...
}
used
数组的作用:used
数组用于记录当前递归层级中已经使用过的数字。- 数组大小为 201,是因为题目中数字的范围是
[-100, 100]
,所以将数字nums[i]
映射到nums[i] + 100
,确保索引非负。
- 优点:
- 不依赖于输入数组的有序性。
- 可以完全避免重复子序列。
去重的完整示例
假设输入数组为 nums = [4, 6, 7, 7]
,去重的过程如下:
-
第一层递归:
- 选择
4
,生成子序列[4]
。 - 选择
6
,生成子序列[6]
。 - 选择
7
,生成子序列[7]
。 - 再次选择
7
,发现7
已经使用过,跳过。
- 选择
-
第二层递归:
- 从
4
开始:- 选择
6
,生成子序列[4, 6]
。 - 选择
7
,生成子序列[4, 7]
。 - 再次选择
7
,发现7
已经使用过,跳过。
- 选择
- 从
6
开始:- 选择
7
,生成子序列[6, 7]
。 - 再次选择
7
,发现7
已经使用过,跳过。
- 选择
- 从
7
开始:- 选择
7
,生成子序列[7, 7]
。
- 选择
- 从
-
结果:
-
最终生成的子序列为:
[4, 6] [4, 7] [6, 7] [7, 7]
-
总结
去重的核心思路是:在同一递归层级中,避免重复使用相同的数字。可以通过以下方法实现:
- 排序 + 跳过重复元素:适用于有序数组。
- 哈希表或数组记录已使用的数字:适用于任意数组,更通用。
本题代码
/*** DFS 函数:递归生成所有非递减子序列* @param nums: 输入数组* @param numsSize: 输入数组的大小* @param start: 当前递归的起始位置* @param path: 当前生成的子序列* @param pathSize: 当前子序列的长度* @param res: 存储所有符合条件的子序列* @param returnSize: 当前找到的子序列的数量* @param returnColumnSizes: 存储每个子序列的长度的数组*/
void dfs(int *nums, int numsSize, int start, int *path, int pathSize, int **res, int *returnSize, int **returnColumnSizes) {// 如果当前子序列的长度大于等于 2,将其加入结果集if (pathSize >= 2) {res[*returnSize] = malloc(sizeof(int) * pathSize); // 为当前子序列分配内存memcpy(res[*returnSize], path, sizeof(int) * pathSize); // 将当前子序列复制到结果集中(*returnColumnSizes)[*returnSize] = pathSize; // 记录当前子序列的长度(*returnSize)++; // 增加结果集的子序列数量}int used[201] = {0}; // 用于去重,记录当前递归层中已经使用过的数字for (int i = start; i < numsSize; i++) {// 如果当前子序列不为空,且当前数字小于子序列的最后一个数字,则跳过(确保子序列是非递减的)if (pathSize > 0 && nums[i] < path[pathSize - 1]) {continue;}// 如果当前数字在当前递归层中已经使用过,则跳过(避免重复子序列)if (used[nums[i] + 100]) {continue;}used[nums[i] + 100] = 1; // 标记当前数字已使用path[pathSize] = nums[i]; // 将当前数字加入子序列dfs(nums, numsSize, i + 1, path, pathSize + 1, res, returnSize, returnColumnSizes); // 递归生成子序列}
}/*** 主函数:找到所有非递减子序列* @param nums: 输入数组* @param numsSize: 输入数组的大小* @param returnSize: 用于返回找到的子序列的数量* @param returnColumnSizes: 用于返回每个子序列的长度的数组* @return: 返回一个二维数组,包含所有非递减子序列*/
int** findSubsequences(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) {int **res = malloc(sizeof(int*) * 40000); // 分配结果数组的内存*returnSize = 0; // 初始化子序列数量为 0*returnColumnSizes = malloc(sizeof(int) * 40000); // 分配子序列长度数组的内存int *path = malloc(sizeof(int) * numsSize); // 分配路径数组的内存dfs(nums, numsSize, 0, path, 0, res, returnSize, returnColumnSizes); // 调用 DFS 函数生成子序列free(path); // 释放路径数组的内存return res; // 返回结果数组
}
全排列
46. 全排列
思路:dfs+回溯
/*** Return an array of arrays of size *returnSize.* The sizes of the arrays are returned as *returnColumnSizes array.* Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().*/
void dfs(int *nums, int numsSize, bool *used, int *path, int pathSize, int **res, int *returnSize, int **returnColumnSizes) {// 如果路径长度等于数组长度,说明找到了一个完整的排列if (pathSize == numsSize) {// 为结果数组分配内存res[*returnSize] = malloc(sizeof(int) * pathSize);// 将当前路径复制到结果数组中memcpy(res[*returnSize], path, sizeof(int) * pathSize);// 更新返回列大小数组(*returnColumnSizes)[*returnSize] = pathSize;// 增加返回大小(*returnSize)++;return;}// 遍历数组中的每个数字for (int i = 0; i < numsSize; i++) {// 如果当前数字已经被使用过,则跳过if (used[i]) continue;// 将当前数字加入路径path[pathSize] = nums[i];// 标记当前数字为已使用used[i] = 1;// 递归调用dfs函数,继续寻找下一个数字dfs(nums, numsSize, used, path, pathSize + 1, res, returnSize, returnColumnSizes);// 回溯,将当前数字标记为未使用used[i] = 0;}return;
}int** permute(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) {// 为结果数组分配内存,假设最大排列数为720(6!)int **res = malloc(sizeof(int*) * 720);// 为路径数组分配内存,假设最大路径长度为6int *path = malloc(sizeof(int) * 6);// 为返回列大小数组分配内存*returnColumnSizes = malloc(sizeof(int) * 720);// 初始化返回大小为0*returnSize = 0;// 初始化已使用数组bool used[25] = {0};// 调用dfs函数,开始深度优先搜索dfs(nums, numsSize, used, path, 0, res, returnSize, returnColumnSizes);// 返回结果数组return res;
}
全排列Ⅱ
47. 全排列 II
思路:dfs+回溯+排序+去重
/*** Return an array of arrays of size *returnSize.* The sizes of the arrays are returned as *returnColumnSizes array.* Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().*/// 深度优先搜索函数,用于生成排列
void dfs(int *nums, int numsSize, int *path, int pathSize, int *used, int **res, int *returnSize, int **returnColumnSizes) {// 如果路径长度等于数组长度,说明找到了一个完整的排列if (pathSize == numsSize) {// 为结果数组分配内存res[*returnSize] = malloc(sizeof(int) * pathSize);// 将当前路径复制到结果数组中memcpy(res[*returnSize], path, sizeof(int) * pathSize);// 更新返回列大小数组(*returnColumnSizes)[*returnSize] = pathSize;// 增加返回大小(*returnSize)++;return;}// 遍历输入数组中的每个元素for (int i = 0; i < numsSize; i++) {// 如果当前元素的剩余使用次数为0,跳过if (used[nums[i] + 10] == 0) continue;// 如果当前元素与前一个元素相同,且前一个元素未被使用,跳过if (i > 0 && nums[i] == nums[i - 1] && used[nums[i - 1] + 10] > 0) continue;// 减少当前元素的剩余使用次数used[nums[i] + 10]--;// 将当前元素加入路径path[pathSize] = nums[i];// 递归调用dfs函数,继续寻找下一个元素dfs(nums, numsSize, path, pathSize + 1, used, res, returnSize, returnColumnSizes);// 回溯,增加当前元素的剩余使用次数used[nums[i] + 10]++;}
}// 比较函数,用于qsort排序
int cmp(const void *a, const void *b) {return *(int *)a - *(int *)b;
}// 主函数,生成所有不重复的排列
int** permuteUnique(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) {// 对输入数组进行排序qsort(nums, numsSize, sizeof(int), cmp);// 计算可能的排列总数int total = 1;for (int i = 2; i <= numsSize; i++) total *= i;// 为结果数组分配内存int **res = malloc(sizeof(int *) * total);// 为路径数组分配内存int *path = malloc(sizeof(int) * numsSize);// 初始化计数数组int used[25] = {0};// 统计每个数字的出现次数for (int i = 0; i < numsSize; i++) {used[nums[i] + 10]++;}// 初始化返回大小*returnSize = 0;// 为返回列大小数组分配内存*returnColumnSizes = malloc(sizeof(int) * total);// 调用dfs函数,开始深度优先搜索dfs(nums, numsSize, path, 0, used, res, returnSize, returnColumnSizes);// 返回结果数组return res;
}
重新安排行程
332. 重新安排行程
题目描述
本题第一个挑战就是读懂题。。。下面是题目大意:
给定一个机票的字符串二维数组 tickets
,其中 tickets[i] = [fromi, toi]
表示一张从 fromi
飞往 toi
的机票。要求重新构建行程,使得所有的机票都被使用一次且仅使用一次。行程必须从 JFK
开始,并且如果有多个有效的行程,返回字典序最小的那个。
思路
- 构建邻接表:将每个机场的邻接机场按字典序逆序排列,以便在DFS时优先访问字典序较小的机场。
- 深度优先搜索(DFS):从JFK出发,递归访问每个机场的邻接机场,直到无法继续为止。每次访问后移除该边,确保每条边只使用一次。
- 结果处理:DFS遍历是后序遍历,因此结果需要反转才能得到正确的行程顺序
代码逻辑
-
机场ID映射:
- 使用
str2id
将机场字符串(如 “JFK”)转换为唯一的整数ID。 - 使用
id2str
将ID映射回字符串,方便最终结果的输出。
- 使用
-
机票排序:
- 将机票转换为
{起点ID, 终点ID}
的形式。 - 使用
qsort
对机票按起点和终点降序排序,确保DFS优先访问字典序较小的机场。
- 将机票转换为
-
邻接表构建:
- 使用
vec
数组存储每个机场的邻接机场。 vec_len
记录每个机场的邻接机场数量。
- 使用
-
DFS遍历:
- 从 “JFK” 开始递归遍历图。
- 每次访问一个机场时,将其邻接机场按顺序加入栈中。
- 最终栈中存储的是后序遍历的结果。
-
结果处理:
- 将栈中的结果反转,得到正确的行程顺序。
- 返回结果数组。
代码1:官方题解
// 全局变量:用于将机场ID映射回字符串
char* id2str[26 * 26 * 26];// 将机场字符串转换为唯一的ID
int str2id(char* a) {int ret = 0;for (int i = 0; i < 3; i++) {ret = ret * 26 + a[i] - 'A'; // 将字符转换为0-25的数字,并计算唯一ID}return ret;
}// 比较函数:用于对机票进行排序
int cmp(const void* _a, const void* _b) {int **a = (int**)_a, **b = (int**)_b;// 先按起点排序,起点相同则按终点排序(降序)return (*b)[0] - (*a)[0] ? (*b)[0] - (*a)[0] : (*b)[1] - (*a)[1];
}// 邻接表:存储每个机场的邻接机场
int* vec[26 * 26 * 26];
int vec_len[26 * 26 * 26]; // 每个机场的邻接机场数量// 栈:用于存储DFS遍历的结果
int* stk;
int stk_len; // 栈的长度// DFS函数:递归遍历图
void dfs(int curr) {// 遍历当前机场的所有邻接机场while (vec_len[curr] > 0) {int tmp = vec[curr][--vec_len[curr]]; // 取出最后一个邻接机场dfs(tmp); // 递归访问}// 将当前机场加入栈中(后序遍历)stk[stk_len++] = curr;
}// 主函数:重新安排行程
char** findItinerary(char*** tickets, int ticketsSize, int* ticketsColSize, int* returnSize) {// 初始化邻接表的长度memset(vec_len, 0, sizeof(vec_len));// 初始化栈stk = malloc(sizeof(int) * (ticketsSize + 1));stk_len = 0;// 将机票转换为ID形式,方便处理int* tickets_tmp[ticketsSize];for (int i = 0; i < ticketsSize; i++) {tickets_tmp[i] = (int*)malloc(sizeof(int) * 2);tickets_tmp[i][0] = str2id(tickets[i][0]); // 起点IDtickets_tmp[i][1] = str2id(tickets[i][1]); // 终点ID// 将ID映射回字符串id2str[tickets_tmp[i][0]] = tickets[i][0];id2str[tickets_tmp[i][1]] = tickets[i][1];}// 对机票进行排序(按起点和终点降序)qsort(tickets_tmp, ticketsSize, sizeof(int*), cmp);// 构建邻接表int add = 0;while (add < ticketsSize) {int adds = add + 1, start = tickets_tmp[add][0];// 找到所有起点相同的机票while (adds < ticketsSize && start == tickets_tmp[adds][0]) {adds++;}// 设置邻接表的长度vec_len[start] = adds - add;// 分配内存并填充邻接表vec[start] = malloc(sizeof(int) * vec_len[start]);for (int i = add; i < adds; i++) {vec[start][i - add] = tickets_tmp[i][1]; // 添加邻接机场}add = adds; // 移动到下一个起点}// 从JFK开始DFS遍历dfs(str2id("JFK"));// 设置返回结果的长度*returnSize = ticketsSize + 1;// 分配内存并填充结果char** ret = malloc(sizeof(char*) * (ticketsSize + 1));for (int i = 0; i <= ticketsSize; i++) {ret[ticketsSize - i] = id2str[stk[i]]; // 反转栈中的结果}return ret;
}
代码2:deepseek r1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>// 定义邻接表节点结构体
typedef struct {char src[4]; // 源机场(如 "JFK")char **dests; // 目标机场列表(邻接表)int dests_size; // 目标机场数量
} AdjEntry;// 比较函数:用于对邻接表中的目标机场按字典序降序排列
int compare(const void *a, const void *b) {return strcmp(*(char **)b, *(char **)a); // 降序排列
}// DFS函数:递归遍历图
void dfs(char *current, AdjEntry *adjList, int adjSize, char **result, int *result_idx) {// 查找当前机场在邻接表中的位置int found = -1;for (int i = 0; i < adjSize; i++) {if (strcmp(adjList[i].src, current) == 0) {found = i;break;}}// 如果当前机场没有邻接机场,直接加入结果if (found == -1) {result[*result_idx] = malloc(4 * sizeof(char));strcpy(result[*result_idx], current);(*result_idx)++;return;}// 遍历当前机场的所有邻接机场while (adjList[found].dests_size > 0) {// 取出最后一个邻接机场(字典序最小)char *next = adjList[found].dests[adjList[found].dests_size - 1];adjList[found].dests_size--; // 移除该机票dfs(next, adjList, adjSize, result, result_idx); // 递归访问}// 将当前机场加入结果(后序遍历)result[*result_idx] = malloc(4 * sizeof(char));strcpy(result[*result_idx], current);(*result_idx)++;
}// 主函数:重新安排行程
char **findItinerary(char ***tickets, int ticketsSize, int *ticketsColSize, int *returnSize) {// 初始化邻接表AdjEntry *adjList = NULL;int adjSize = 0;// 构建邻接表for (int i = 0; i < ticketsSize; i++) {char *src = tickets[i][0]; // 源机场char *dst = tickets[i][1]; // 目标机场int found = -1;// 查找源机场是否已经在邻接表中for (int j = 0; j < adjSize; j++) {if (strcmp(adjList[j].src, src) == 0) {found = j;break;}}// 如果源机场不在邻接表中,添加新条目if (found == -1) {adjSize++;adjList = realloc(adjList, adjSize * sizeof(AdjEntry));strcpy(adjList[adjSize - 1].src, src);adjList[adjSize - 1].dests = NULL;adjList[adjSize - 1].dests_size = 0;found = adjSize - 1;}// 添加目标机场到邻接表adjList[found].dests_size++;adjList[found].dests = realloc(adjList[found].dests, adjList[found].dests_size * sizeof(char *));adjList[found].dests[adjList[found].dests_size - 1] = dst;}// 对每个机场的邻接表按字典序降序排序for (int i = 0; i < adjSize; i++) {qsort(adjList[i].dests, adjList[i].dests_size, sizeof(char *), compare);}// 准备结果数组char **result = malloc((ticketsSize + 1) * sizeof(char *));int result_idx = 0;// 从 JFK 开始执行 DFSdfs("JFK", adjList, adjSize, result, &result_idx);// 反转结果数组(DFS是后序遍历,需要反转)for (int i = 0; i < result_idx / 2; i++) {char *temp = result[i];result[i] = result[result_idx - 1 - i];result[result_idx - 1 - i] = temp;}// 设置返回结果的长度*returnSize = result_idx;// 释放邻接表内存for (int i = 0; i < adjSize; i++) {free(adjList[i].dests);}free(adjList);return result;
}
N皇后
51. N 皇后
思路:回溯,dfs,剪枝
- 回溯法:
- 使用深度优先搜索(DFS)枚举所有可能的解。
- 逐行放置皇后,并在放置时检查是否与之前放置的皇后冲突。
- 如果冲突,则回溯并尝试其他位置。
- 冲突检查:
- 列冲突:检查当前列是否已经有皇后。
- 对角线冲突:检查当前位置的左上方和右上方对角线是否已经有皇后
#include <stdio.h>
#include <stdlib.h>
#include <string.h>/*** DFS函数:递归解决N皇后问题* @param n 棋盘大小* @param path 当前路径(棋盘状态)* @param pathSize 当前路径长度(已放置的皇后数量)* @param res 结果数组* @param returnSize 结果数量* @param returnColumnSizes 每个解的大小*/
void dfs(int n, char **path, int pathSize, char ***res, int *returnSize, int **returnColumnSizes) {// 如果当前路径长度等于n,说明找到一个解if (pathSize == n) {// 分配内存存储当前解res[*returnSize] = malloc(sizeof(char *) * n);for (int i = 0; i < n; i++) {res[*returnSize][i] = malloc(sizeof(char) * (n + 1)); // 每行长度为n+1(包括'\0')for (int j = 0; j < n; j++) {res[*returnSize][i][j] = path[i][j]; // 复制当前行的内容}res[*returnSize][i][n] = '\0'; // 字符串结束符}(*returnColumnSizes)[*returnSize] = n; // 设置当前解的大小(*returnSize)++; // 解的数量加1return;}// 尝试在当前行的每一列放置皇后for (int i = 0; i < n; i++) {int flag = 0; // 冲突标志// 检查当前列是否冲突for (int j = 0; j < pathSize; j++) {if (path[j][i] == 'Q') {flag = 1;break;}}if (flag) continue; // 如果冲突,跳过// 检查右上方对角线是否冲突int h = pathSize - 1, r = i + 1;while (h >= 0 && r < n) {if (path[h][r] == 'Q') {flag = 1;break;}h--;r++;}if (flag) continue; // 如果冲突,跳过// 检查左上方对角线是否冲突h = pathSize - 1, r = i - 1;while (h >= 0 && r >= 0) {if (path[h][r] == 'Q') {flag = 1;break;}h--;r--;}if (flag) continue; // 如果冲突,跳过// 在当前行放置皇后path[pathSize] = malloc(sizeof(char) * (n + 1));for (int j = 0; j < n; j++) {path[pathSize][j] = (j == i) ? 'Q' : '.'; // 放置皇后或空位}path[pathSize][n] = '\0'; // 字符串结束符// 递归处理下一行dfs(n, path, pathSize + 1, res, returnSize, returnColumnSizes);// 回溯:释放当前行的内存free(path[pathSize]);}
}/*** 主函数:解决N皇后问题* @param n 棋盘大小* @param returnSize 返回解的数量* @param returnColumnSizes 返回每个解的大小* @return 所有解的数组*/
char ***solveNQueens(int n, int *returnSize, int **returnColumnSizes) {// 初始化结果数组char ***res = malloc(sizeof(char **) * 1000); // 假设最多1000个解*returnSize = 0; // 解的数量初始化为0*returnColumnSizes = malloc(sizeof(int) * 1000); // 每个解的大小// 初始化路径数组char **path = malloc(sizeof(char *) * n); // 路径数组,存储当前棋盘状态for (int i = 0; i < n; i++) {path[i] = malloc(sizeof(char) * (n + 1)); // 每行长度为n+1(包括'\0')memset(path[i], '.', n); // 初始化为空位path[i][n] = '\0'; // 字符串结束符}// 调用DFS函数dfs(n, path, 0, res, returnSize, returnColumnSizes);// 释放路径数组的内存for (int i = 0; i < n; i++) {free(path[i]);}free(path);return res;
}
解数独
37. 解数独
思路:dfs
对于空数(为.
)的地方,依次尝试填充1到9,只要找到一种正确答案就返回。dfs可以返回bool
型
#include <stdbool.h>
#include <stdio.h>// 检查当前数字是否合法
bool isvaild(char **board, int col, int row, char x) {// 检查行for (int i = 0; i < 9; i++) {if (board[col][i] == x) return false;}// 检查列for (int i = 0; i < 9; i++) {if (board[i][row] == x) return false;}// 检查3x3子网格int startCol = (col / 3) * 3;int startRow = (row / 3) * 3;for (int i = startCol; i < startCol + 3; i++) {for (int j = startRow; j < startRow + 3; j++) {if (board[i][j] == x) return false;}}return true;
}// 回溯函数:递归填充数独板
bool dfs(char **board, int boardSize, int col, int row) {// 遍历数独板的每个格子for (int i = col; i < boardSize; i++) {for (int j = row; j < boardSize; j++) {// 如果当前格子为空if (board[i][j] == '.') {// 尝试填入1到9的数字for (char k = '1'; k <= '9'; k++) {// 检查当前数字是否合法if (isvaild(board, i, j, k)) {// 填入数字board[i][j] = k;// 递归填充下一个格子if (dfs(board, boardSize, i, j)) {return true; // 找到解,提前返回}// 回溯:撤销当前选择board[i][j] = '.';}}return false; // 当前格子无解,触发回溯}}row = 0; // 重置列索引,确保从下一行的开头开始}return true; // 所有格子填充完毕,找到解
}// 主函数:解决数独问题
void solveSudoku(char **board, int boardSize, int *boardColSize) {for(int i=0;i<9;i++)boardColSize[i]=9;dfs(board, boardSize, 0, 0);return;
}
startCol = (col / 3) * 3;
int startRow = (row / 3) * 3;
for (int i = startCol; i < startCol + 3; i++) {
for (int j = startRow; j < startRow + 3; j++) {
if (board[i][j] == x) return false;
}
}
return true;
}
// 回溯函数:递归填充数独板
bool dfs(char **board, int boardSize, int col, int row) {
// 遍历数独板的每个格子
for (int i = col; i < boardSize; i++) {
for (int j = row; j < boardSize; j++) {
// 如果当前格子为空
if (board[i][j] == ‘.’) {
// 尝试填入1到9的数字
for (char k = ‘1’; k <= ‘9’; k++) {
// 检查当前数字是否合法
if (isvaild(board, i, j, k)) {
// 填入数字
board[i][j] = k;
// 递归填充下一个格子
if (dfs(board, boardSize, i, j)) {
return true; // 找到解,提前返回
}
// 回溯:撤销当前选择
board[i][j] = ‘.’;
}
}
return false; // 当前格子无解,触发回溯
}
}
row = 0; // 重置列索引,确保从下一行的开头开始
}
return true; // 所有格子填充完毕,找到解
}
// 主函数:解决数独问题
void solveSudoku(char **board, int boardSize, int *boardColSize) {
for(int i=0;i<9;i++)boardColSize[i]=9;
dfs(board, boardSize, 0, 0);
return;
}
相关文章:
代码随想录——回溯
文章目录 组合组合总数电话号码的字母组合组合总数组合总数Ⅱ分割回文串复原IP地址子集子集Ⅱ非递减子序列去重的实现方法方法 1:**排序 跳过重复元素**方法 2:**使用哈希表或数组记录已使用的数字** 去重的完整示例总结本题代码 全排列全排列Ⅱ重新安排…...
独立游戏RPG回顾:高成本
刚看了某纪录片, 内容是rpg项目的回顾。也是这个以钱为核心话题的系列的最后一集。 对这期特别有代入感,因为主角是曾经的同事,曾经在某天晚上听过其项目组的争论。 对其这些年的起伏特别的能体会。 主角是制作人,在访谈中透露这…...
SQLModel入门
目录 概述快速开始官方教程简单使用样例 概述 SQLModel 是一个 ORM 框架,其基于 SQLAlchemy 和 Pydantic,其中 SQLALchemy 提供底层 ORM 能力,Pydantic 提供类型校验能力,SQLModel 中,一个 SQLModel model 既是一个 S…...
关于MySQL InnoDB存储引擎的一些认识
文章目录 一、存储引擎1.MySQL中执行一条SQL语句的过程是怎样的?1.1 MySQL的存储引擎有哪些?1.2 MyIsam和InnoDB有什么区别? 2.MySQL表的结构是什么?2.1 行结构是什么样呢?2.1.1 NULL列表?2.1.2 char和varc…...
【学习笔记】深度学习网络-正则化方法
作者选择了由 Ian Goodfellow、Yoshua Bengio 和 Aaron Courville 三位大佬撰写的《Deep Learning》(人工智能领域的经典教程,深度学习领域研究生必读教材),开始深度学习领域学习,深入全面的理解深度学习的理论知识。 在之前的文章中介绍了深度学习中用…...
NVIDIA (英伟达)的 GPU 产品应用领域
游戏娱乐领域 PC 游戏:NVIDIA 的 GeForce 系列 GPU 是 PC 游戏玩家的首选之一。能实现实时光线追踪、高分辨率渲染等,使游戏画面更加逼真,如《赛博朋克 2077》等支持光线追踪的游戏,在 NVIDIA GPU 的加持下,可呈现出真…...
Docker快速部署高效照片管理系统LibrePhotos搭建私有云相册
文章目录 前言1.关于LibrePhotos2.本地部署LibrePhotos3.LibrePhotos简单使用4. 安装内网穿透5.配置LibrePhotos公网地址6. 配置固定公网地址 前言 想象一下这样的场景:你有一大堆珍贵的回忆照片,但又不想使用各种网盘来管理。怎么办?别担心…...
goframe 多语言国际化解决方案
项目背景 本项目采用基于JSON配置的多语言国际化(i18n)解决方案,支持多种语言的无缝切换和本地化。 目录结构 manifest/ └── i18n/├── zh.json # 简体中文├── zh-tw.json # 繁体中文├── en.json # 英语├…...
mysql如何修改密码
在MySQL中修改密码可以通过多种方式完成,具体取决于你的MySQL版本和你是否有足够的权限。以下是一些常用的方法来修改MySQL用户的密码: 方法1: 使用ALTER USER命令 这是最常用的方法,适用于MySQL 5.7及以上版本。 ALTER USER usernameloca…...
17.2 图形绘制8
版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的。 17.2.10 重绘 先看以下例子: 【例 17.28】【项目:code17-028】绘制填充矩形。 private void button1_Clic…...
Java基础知识总结(三十八)--读取数据
使用Reader体系,读取一个文本文件中的数据。返回 -1 ,标志读到结尾。 import java.io.*; class { public static void main(String[] args) throws IOException { /* 创建可以读取文本文件的流对象,让创建好的流对象和指定的文件相关联。…...
【并查集】
并查集(Disjoint Set Union,DSU)是一种用于处理不相交集合的数据结构,主要支持两种操作:查找(Find)和合并(Union)。它在解决连通性问题、图论问题以及动态连通性等问题时…...
SQL NOW() 函数详解
SQL NOW() 函数详解 引言 在SQL数据库中,NOW() 函数是一个常用的日期和时间函数,用于获取当前的时间戳。本文将详细介绍 NOW() 函数的用法、参数、返回值以及在实际应用中的注意事项。 函数概述 NOW() 函数返回当前的日期和时间,格式为 Y…...
[EAI-023] FAST,机器人动作专用的Tokenizer,提高VLA模型的能力和训练效率
Paper Card 论文标题:FAST: Efficient Action Tokenization for Vision-Language-Action Models 论文作者:Karl Pertsch, Kyle Stachowicz, Brian Ichter, Danny Driess, Suraj Nair, Quan Vuong, Oier Mees, Chelsea Finn, Sergey Levine 论文链接&…...
Rust 条件语句
Rust 条件语句 在编程语言中,条件语句是进行决策和实现分支逻辑的关键。Rust 语言作为一门系统编程语言,其条件语句的使用同样至关重要。本文将详细介绍 Rust 中的条件语句,包括其基本用法、常见场景以及如何避免常见错误。 基本用法 Rust…...
Windows 上安装 PostgreSQL
Windows 上安装 PostgreSQL PostgreSQL 是一款功能强大的开源对象-关系型数据库系统,它具有出色的扩展性和稳定性。本文将详细介绍在 Windows 操作系统上安装 PostgreSQL 的步骤和注意事项。 1. 准备工作 在开始安装 PostgreSQL 之前,请确保您的计算机满足以下要求: 操作…...
UE 5.3 C++ 对垃圾回收的初步认识
一.UObject的创建 UObject 不支持构造参数。 所有的C UObject都会在引擎启动的时候初始化,然后引擎会调用其默认构造器。如果没有默认的构造器,那么 UObject 将不会编译。 有修改父类参数的需求,就使用指定带参构造 // Sets default value…...
解码,蓝桥杯2020G
a2b 解码后:aab #include<iostream> using namespace std; typedef struct Node {char data;int size;Node* next; }Node,*Linklist; char* scan(char str[],int size) {int i 0;Linklist head new Node;Linklist rear head;while (i<size-1) {Lin…...
【贪心算法篇】:“贪心”之旅--算法练习题中的智慧与策略(一)
✨感谢您阅读本篇文章,文章内容是个人学习笔记的整理,如果哪里有误的话还请您指正噢✨ ✨ 个人主页:余辉zmh–CSDN博客 ✨ 文章所属专栏:贪心算法篇–CSDN博客 文章目录 一.贪心算法1.什么是贪心算法2.贪心算法的特点 二.例题1.柠…...
Python3 + Qt5:实现AJAX异步更新UI
使用 Python 和 Qt5 开发时异步加载数据的方法 在开发使用 Python 和 Qt5 的应用程序时,为了避免在加载数据时界面卡顿,可以采用异步加载的方式。以下是几种实现异步加载的方法: 1. 使用多线程(QThread) 通过将数据…...
Windows系统中Docker可视化工具对比分析,Docker Desktop,Portainer,Rancher
Docker可视化工具对比分析,Docker Desktop,Portainer,Rancher Windows系统中Docker可视化工具对比分析1. 工具概览2. Docker Desktop官网链接:主要优点:主要缺点:版本更新频率: 3. Portainer官网…...
从ai产品推荐到利用cursor快速掌握一个开源项目再到langchain手搓一个Text2Sql agent
目录 0. 经验分享:产品推荐 1. 经验分享:提示词优化 2. 经验分享:使用cursor 阅读一篇文章 3. 经验分享:使用cursor 阅读一个完全陌生的开源项目 4. 经验分享:手搓一个text2sql agent (使用langchain l…...
curope python安装
目录 curope安装 测试: 报错:libc10.so: cannot open shared object file: No such file or directory 解决方法: curope安装 git clone : GitHub - Junyi42/croco at bd6f4e07d5c4f13ae5388efc052dadf142aff754 cd models/curope/ python setup.py build_ext --inplac…...
低代码产品插件功能一览
下图是统计的目前市面上流行的低代码、零代码产品的插件功能。 产品名称 产品类型 官方插件数量 支持拓展 官方插件功能 宜搭 零代码 3 暂不支持 云打印、CAD看图、打印表单详情 微搭 低代码 1 暂不支持 小程序 明道云 低代码 2 支持 视图、工作流节点 简道…...
流浪 Linux: 外置 USB SSD 安装 ArchLinux
注: ArchLinux 系统为滚动更新, 变化很快, 所以本文中的安装方法可能很快就过时了, 仅供参考. 实际安装时建议去阅读官方文档. 最近, 突然 (也没有那么突然) 有了一大堆 PC: 4 个笔记本, 2 个台式主机 (M-ATX 主板), 1 个小主机 (迷你主机). 嗯, 多到用不过来. 但是, 窝又不能…...
开启 AI 学习之旅:从入门到精通
最近 AI 真的超火,不管是工作还是生活里,到处都能看到它的身影。好多小伙伴都跑来问我,到底该怎么学 AI 呢?今天我就把自己学习 AI 的经验和心得分享出来,希望能帮到想踏入 AI 领域的朋友们! 一、学习内容有哪些 (一)编程语言 Python 绝对是首选!它在 AI 领域的生态…...
笔记:使用ST-LINK烧录STM32程序怎么样最方便?
一般板子在插件上, 8脚 3.3V;9脚 CLK;10脚 DIO;4脚GND ST_Link 19脚 3.3V;9脚 CLK;7脚 DIO;20脚 GND 烧录软件:ST-LINK Utility,Keil_5; ST_Link 接口针脚定义: 按定义连接ST_Link与电路板; 打开STM32 ST-LINK Uti…...
开发环境搭建-4:WSL 配置 docker 运行环境
在 WSL 环境中构建:WSL2 (2.3.26.0) Oracle Linux 8.7 官方镜像 基本概念说明 容器技术 利用 Linux 系统的 文件系统(UnionFS)、命名空间(namespace)、权限管理(cgroup),虚拟出一…...
925.长按键入
目录 一、题目二、思路三、解法四、收获 一、题目 你的朋友正在使用键盘输入他的名字 name。偶尔,在键入字符 c 时,按键可能会被长按,而字符可能被输入 1 次或多次。 你将会检查键盘输入的字符 typed。如果它对应的可能是你的朋友的名字&am…...
【数据采集】案例01:基于Scrapy采集豆瓣电影Top250的详细数据
基于Scrapy采集豆瓣电影Top250的详细数据 Scrapy 官方文档:https://docs.scrapy.org/en/latest/豆瓣电影Top250官网:https://movie.douban.com/top250写在前面 实验目的:基于Scrapy框架采集豆瓣电影Top250的详细数据。 电脑系统:Windows 使用软件:PyCharm、Navicat Python…...
doris:主键模型的导入更新
这篇文档主要介绍 Doris 主键模型基于导入的更新。 整行更新 使用 Doris 支持的 Stream Load、Broker Load、Routine Load、Insert Into 等导入方式,向主键模型(Unique 模型)导入数据时,如果没有相应主键的数据行,…...
日志2025.2.1
日志2025.2.1 1.做了敌人状态机 public class EnermyStateMachine { public EnermyState currentState { get; private set; } public void InitializeState(EnermyState startState) { currentState startState; currentState.Enter(); } public void Change…...
RK3568使用QT操作LED灯
文章目录 一、QT中操作硬件设备思路Linux 中的设备文件操作硬件设备的思路1. 打开设备文件2. 写入数据到设备3. 从设备读取数据4. 设备控制5. 异常处理在 Qt 中操作设备的典型步骤实际应用中的例子:控制 LED总结二、QT实战操作LED灯设备1. `mainwindow.h` 头文件2. `mainwindo…...
Rank-analysis-1.2——一款基于LCU API的排位分析工具,大四学生独立开发
LOL Rank Record Analysis:一款基于LCU API的排位分析工具,大四学生独立开发! 大家好!我是河南科技学院的大四学生,今天给大家分享一个我自己开发的软件——LOL Rank Record Analysis。这是一个基于 Riot 提供的 LCU …...
关于系统重构实践的一些思考与总结
文章目录 一、前言二、系统重构的范式1.明确目标和背景2.兼容屏蔽对上层的影响3.设计灰度迁移方案3.1 灰度策略3.2 灰度过程设计3.2.1 case1 业务逻辑变更3.2.2 case2 底层数据变更(数据平滑迁移)3.2.3 case3 在途新旧流程兼容3.2.4 case4 接口变更3.2.5…...
代码随想录-训练营-day17
235. 二叉搜索树的最近公共祖先 - 力扣(LeetCode) /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode(int x) : val(x), left(NULL), right(NULL) {}* };*/class S…...
C++,STL,【目录篇】
文章目录 一、简介二、内容提纲第一部分:STL 概述第二部分:STL 容器第三部分:STL 迭代器第四部分:STL 算法第五部分:STL 函数对象第六部分:STL 高级主题第七部分:STL 实战应用 三、写作风格四、…...
《数据可视化新高度:Graphy的AI协作变革》
在数据洪流奔涌的时代,企业面临的挑战不再仅仅是数据的收集,更在于如何高效地将数据转化为洞察,助力决策。Graphy作为一款前沿的数据可视化工具,凭借AI赋能的团队协作功能,为企业打开了数据协作新局面,重新…...
abc 390 D(暴搜 复杂度用 bell数 证明 n 的集合的划分方法的数目)
D题意: 将 长度为 N 的数组 划分为集合 有多少种不同的 异或和 这道题做出来和bell 数没什么关系,如果要证明复杂度那么就需要bell 数 #include <bits/stdc.h> using namespace std; typedef pair<int, int> PII; #define int long long i…...
AJAX综合案例——图书管理
黑马程序员视频地址: AJAX-Day02-10.案例_图书管理AJAX-Day02-10.案例_图书管理_总结_V1.0是黑马程序员前端AJAX入门到实战全套教程,包含学前端框架必会的(ajaxnode.jswebpackgit),一套全覆盖的第25集视频,…...
计算机网络 笔记 网络层 3
IPv6 IPv6 是互联网协议第 6 版(Internet Protocol Version 6)的缩写,它是下一代互联网协议,旨在解决 IPv4 面临的一些问题,以下是关于 IPv6 的详细介绍: 产生背景: 随着互联网的迅速发展&…...
Web服务器启动难题:Spring Boot框架下的异常处理解析
摘要 在尝试启动Web服务器时遇到了无法启动的问题,具体错误为org.springframework.boot.web.server.WebServerException。这一异常表明Spring Boot框架在初始化Web服务器过程中出现了故障。通常此类问题源于配置文件错误、端口冲突或依赖项缺失等。排查时应首先检查…...
在LINUX上安装英伟达CUDA Toolkit
下载安装包 wget https://developer.download.nvidia.com/compute/cuda/12.8.0/local_installers/cuda-repo-rhel8-12-8-local-12.8.0_570.86.10-1.x86_64.rpm 安装RPM包 sudo rpm -i cuda-repo-rhel8-12-8-local-12.8.0_570.86.10-1.x86_64.rpm sudo dnf clean all sudo dnf…...
计算机视觉和图像处理
计算机视觉与图像处理的最新进展 随着人工智能技术的飞速发展,计算机视觉和图像处理作为其中的重要分支,正逐步成为推动科技进步和产业升级的关键力量。 一、计算机视觉的最新进展 计算机视觉,作为人工智能的重要分支,主要研究如…...
Spring Boot 日志:项目的“行车记录仪”
一、什么是Spring Boot日志 (一)日志引入 在正式介绍日志之前,我们先来看看上篇文章中(Spring Boot 配置文件)中的验证码功能的一个代码片段: 这是一段校验用户输入的验证码是否正确的后端代码,…...
ComfyUI中For Loop的使用
研究了半天,终于弄明白了如何使用For Loop。 1、在For中节点,必须有输出连接到For Loop End的initial_value点,才能确保节点执行完毕后才 进入下一轮循环,否则,可能导致节点没执行完,就进入下一个循环了。…...
网站快速收录:利用网站导航优化用户体验
本文转自:百万收录网 原文链接:https://www.baiwanshoulu.com/44.html 网站快速收录与用户体验的提升密切相关,而网站导航作为用户访问网站的“指南针”,其优化对于实现这一目标至关重要。以下是一些利用网站导航优化用户体验&am…...
FLTK - FLTK1.4.1 - 搭建模板,将FLTK自带的实现搬过来做实验
文章目录 FLTK - FLTK1.4.1 - 搭建模板,将FLTK自带的实现搬过来做实验概述笔记my_fltk_test.cppfltk_test.hfltk_test.cxx用adjuster工程试了一下,好使。END FLTK - FLTK1.4.1 - 搭建模板,将FLTK自带的实现搬过来做实验 概述 用fluid搭建UI…...
c#aot做跨平台动态库
c#aot技术,很多人都觉得是垃圾,没有用,其实还是很有用的。.net发展这么多年,有很多很好的功能,你可以把这些功能做成动态库供rust调用,供c/c调用。你还真别看不起这些功能,当你需要,…...
使用Pygame制作“打砖块”游戏
1. 前言 打砖块(Breakout / Arkanoid) 是一款经典街机游戏,玩家控制一个可左右移动的挡板,接住并反弹球,击碎屏幕上方的砖块。随着砖块被击碎,不仅能获得分数,还可以体验到不断加速或复杂的反弹…...