方案一:
cpp
复制代码
class Solution {
public:
// tmp: 标记数组,记录nums中每个元素是否已被使用
// tmp[i] = false 表示nums[i]未被使用,true表示已被使用
vector<bool> tmp;
// path: 当前正在构建的排列路径(临时存储当前DFS路径上的元素)
vector<int> path;
// ret: 结果集合,使用set自动去重(因为nums可能有重复元素)
// set会自动排序并去重相同的vector,避免重复排列
set<vector<int>> ret;
// 主函数:输入可能包含重复数字的数组,返回所有不重复的全排列
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<vector<int>> su; // 最终返回的结果数组
int n = nums.size();
// 初始化tmp数组,所有元素初始状态为false(都未使用)
for(int i = 0; i < n; ++i) {
tmp.push_back(false);
}
// 启动深度优先搜索
dfs(nums);
// 将set中的结果转移到vector中返回
// (因为set的迭代器遍历得到的是有序、去重后的结果)
for(auto& i : ret) {
su.push_back(i);
}
return su;
}
// 深度优先搜索函数:递归构建所有可能的排列
void dfs(vector<int>& nums) {
// 递归终止条件:当前路径长度等于原数组长度
// 说明已经选够了n个元素,形成一个完整排列
if (path.size() == nums.size()) {
ret.insert(path); // 将当前排列加入结果集(set自动去重)
return; // 返回上一层,继续搜索其他分支
}
// 遍历所有元素,尝试将未使用的元素加入当前排列
for(int i = 0; i < tmp.size(); ++i) {
// 剪枝:如果当前元素已被使用,则跳过
if(tmp[i] == false) {
// 做选择:将nums[i]加入当前路径
path.push_back(nums[i]);
// 标记该元素已被使用
tmp[i] = true;
// 递归进入下一层,继续填充下一个位置
dfs(nums);
// 撤销选择:回溯,恢复状态(关键步骤!)
tmp[i] = false; // 标记为未使用
path.pop_back(); // 从路径中移除该元素
}
}
// 循环结束,当前层的所有可能选择都已尝试完毕
}
};
方案二:
cpp
复制代码
class Solution {
// path: 当前正在构建的排列路径
vector<int> path;
// ret: 存储所有不重复的全排列结果
vector<vector<int>> ret;
// check: 标记数组,check[i]表示nums[i]是否已被使用
// 大小固定为9(根据题目约束,nums长度不超过8,所以9足够)
bool check[9];
public:
// 主函数:输入可能包含重复数字的数组,返回所有不重复的全排列
vector<vector<int>> permuteUnique(vector<int>& nums) {
// 关键步骤1:先对数组排序,让相同元素相邻
// 这样便于后续剪枝去重
sort(nums.begin(), nums.end());
// 初始化check数组为false(C++全局/成员数组默认可能不为0,但这里依赖默认初始化)
// 实际上应该显式初始化:memset(check, 0, sizeof(check));
// 从第0个位置开始填充
dfs(nums, 0);
return ret;
}
// 深度优先搜索
// pos: 当前要填充的位置(0, 1, 2, ... , nums.size()-1)
void dfs(vector<int>& nums, int pos) {
// 递归终止条件:所有位置都已填充完毕
if(pos == nums.size()) {
ret.push_back(path); // 保存当前完整排列
return;
}
// 遍历所有元素,尝试将未使用的元素填入当前pos位置
for(int i = 0; i < nums.size(); i++) {
// 【核心剪枝逻辑】判断能否使用nums[i]
// 条件分解:
// 1. check[i] == false: nums[i]未被使用(基本前提)
//
// 2. i == 0 || nums[i] != nums[i-1] || check[i-1] != false
// 这个条件确保不会生成重复排列,分为三种情况:
//
// 情况A: i == 0
// 第一个元素,前面没有元素,可以直接使用
//
// 情况B: nums[i] != nums[i-1]
// 当前元素与前一个元素不同,不会产生重复,可以使用
//
// 情况C: check[i-1] != false (即check[i-1] == true)
// 当前元素与前一个元素相同(nums[i] == nums[i-1])
// 但前一个相同元素已经被使用了
// 这意味着当前是在构建"第一个该元素"的子树,可以使用
//
// 隐含排除的情况:
// nums[i] == nums[i-1] 且 check[i-1] == false
// 前一个相同元素还没被用,当前却要用这个相同的元素
// 这会导致重复排列,必须跳过!
if(check[i] == false && (i == 0 || nums[i] != nums[i - 1] || check[i - 1] != false)) {
// 做选择:将nums[i]加入路径,标记为已使用
path.push_back(nums[i]);
check[i] = true;
// 递归填充下一个位置
dfs(nums, pos + 1);
// 撤销选择:回溯,恢复现场
path.pop_back(); // 从路径移除
check[i] = false; // 标记为未使用
}
}
}
};
cpp
复制代码
class Solution {
public:
// 映射表:数字到对应字母字符串的映射
// 例如:按数字键'2'可以选择'a','b','c'中的一个
map<char, string> m = {
{'2', "abc"},
{'3', "def"},
{'4', "ghi"},
{'5', "jkl"},
{'6', "mno"},
{'7', "pqrs"},
{'8', "tuv"},
{'9', "wxyz"}
};
// ret: 存储所有可能的字母组合结果
vector<string> ret;
// path: 当前正在构建的字母组合路径(临时字符串)
string path;
// 主函数:输入数字字符串,返回所有可能的字母组合
// 例如输入"23",返回["ad","ae","af","bd","be","bf","cd","ce","cf"]
vector<string> letterCombinations(string digits) {
int n = digits.size();
// 处理空输入的边界情况
if (n == 0) {
return ret; // 返回空数组
}
// 从第0个数字开始深度优先搜索
dfs(digits, 0);
return ret;
}
// 深度优先搜索函数
// digits: 输入的数字字符串
// pos: 当前要处理的数字位置(0, 1, 2, ...)
void dfs(string& digits, int pos) {
// 递归终止条件:当前组合长度等于数字串长度
// 说明已经处理完所有数字,形成一个完整组合
if (path.size() == digits.size()) {
ret.push_back(path); // 将当前组合加入结果集
return; // 返回上一层,继续搜索其他分支
}
// 获取当前位置对应的数字字符
char c = digits[pos];
// 通过映射表获取该数字对应的所有可选字母
// 例如:c='2'时,s="abc"
string s = m[c];
// 遍历该数字对应的所有字母
for (int i = 0; i < s.size(); ++i) {
// 做选择:将当前字母加入路径
path += s[i];
// 递归处理下一个数字位置
dfs(digits, pos + 1);
// 撤销选择:回溯,移除最后加入的字母
// 这样path才能复用,尝试该数字的下一个字母
path.pop_back();
}
// 循环结束,当前数字的所有字母选择都已尝试完毕
}
};
cpp
复制代码
#include <vector>
using namespace std;
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* @param board string字符串vector 二维字符网格
* @param word string字符串 要搜索的目标单词
* @return bool布尔型 是否存在这样的路径
*/
// path: 当前构建的路径字符串(记录已匹配的字符序列)
string path;
// check: 访问标记数组,check[i][j]表示board[i][j]是否已被当前路径使用
// 大小101x101足够覆盖一般题目约束(通常board不超过100x100)
bool check[101][101] = {false};
// ret: 结果标志,一旦找到匹配路径立即设为true,用于提前终止所有搜索
bool ret = false;
// 主函数:在二维网格中搜索是否存在一条路径能组成目标单词
// 路径要求:相邻单元格必须上下左右相邻,且每个单元格只能使用一次
bool exist(vector<string>& board, string word) {
// 获取起始字符(单词的第一个字符)
char c = word[0];
int i, j;
// 遍历网格中的每一个单元格作为起点
for (i = 0; i < board.size(); ++i) {
for (j = 0; j < board[0].size(); ++j) {
// 从位置(i,j)开始深度优先搜索
dfs(board, word, i, j, 0);
// 提前退出:如果已经找到答案,无需继续搜索
if (ret)
return true;
}
}
// 所有起点都尝试完毕,仍未找到匹配路径
return false;
}
// 深度优先搜索函数
// board: 二维字符网格
// word: 目标单词
// i, j: 当前在网格中的行坐标和列坐标
// k: 当前要匹配的目标字符在word中的索引(0, 1, 2, ...)
void dfs(vector<string>& board, string word, int i, int j, int k) {
// 【剪枝1】边界检查:坐标越界则直接返回
// i < 0: 超出上边界
// i >= board.size(): 超出下边界
// j < 0: 超出左边界
// j >= board[0].size(): 超出右边界
if (i < 0 || i >= board.size() || j < 0 || j >= board[0].size())
return;
// 【剪枝2】访问检查与字符匹配检查
// check[i][j] == true: 当前单元格已被当前路径使用,不能重复使用
// board[i][j] != word[k]: 当前单元格字符与目标字符不匹配
if (check[i][j] || board[i][j] != word[k])
return;
// 【剪枝3】全局结果检查:如果已经找到答案,立即停止所有递归
// 避免不必要的搜索,优化性能
if (ret)
return;
// 【做选择】当前单元格符合要求,加入当前路径
path.push_back(board[i][j]);
check[i][j] = true; // 标记为已访问
// 【检查是否完成】当前路径已完全匹配目标单词
if (path == word) {
ret = true; // 设置全局标志,通知所有递归层已找到答案
}
// 【递归搜索】向四个方向(上下左右)探索下一个字符
// 注意:k+1表示要匹配word的下一个字符
dfs(board, word, i - 1, j, k + 1); // 上
dfs(board, word, i + 1, j, k + 1); // 下
dfs(board, word, i, j - 1, k + 1); // 左
dfs(board, word, i, j + 1, k + 1); // 右
// 【撤销选择】回溯,恢复状态,尝试其他路径
check[i][j] = false; // 取消访问标记
path.pop_back(); // 从当前路径移除该字符
}
};