- 简单的情况
组合问题,待选区间为[1, n]。每一次组合都从当前层的下一个开始选,避免重复。不存在去重问题。
for (int i = startIndex; i <= n; i++) { // 控制树的横向遍历
path.push_back(i); // 处理节点
backtracking(n, k, i + 1); // 递归:控制树的纵向遍历,注意下一层搜索要从i+1开始
path.pop_back(); // 回溯,撤销处理的节点
}
核心代码如上,进入回溯算法时,传入参数i+1,表示在树的下一层从下一个节点开始选择。
- 递增子序列
任务:给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。 要注意的第一点是,在path.size() >= 2的时候不要return。否则就不会搜集到3个或者4个的结果。 要思考的另一个点是如何避免重复的子序列。因为无法排序,所以无法通过nums[i] == nums[i - 1] 来进行判断。这里利用unordered_set数据结构来进行标记是否要对这个节点进行遍历。 最后一个问题是,思考如何进行判断,使得最后搜集起来的Path是不减的。
unordered_set<int> uset; // 使用set来对本层元素进行去重
for (int i = startIndex; i < nums.size(); i++) {
if ((!path.empty() && nums[i] < path.back())
|| uset.find(nums[i]) != uset.end()) {
continue;
}
uset.insert(nums[i]); // 记录这个元素在本层用过了,本层后面不能再用了
path.push_back(nums[i]);
backtracking(nums, i + 1);
path.pop_back();
}
- 全排列 一开始写全排列的时候也没写出来,卡在了每一层搜索上,从0开始选择下一个节点,那么如何避免选择到自己。另外加一层数据结构:数组来标记当前的树是否用过,如果用过那么在单层搜索中直接continue。
for (int i = 0; i < nums.size(); i++) {
if (used[i] == true) continue; // path里已经收录的元素,直接跳过
used[i] = true;
path.push_back(nums[i]);
backtracking(nums, used);
path.pop_back();
used[i] = false;
}
- 有重复元素的全排列问题
for (int i = 0; i < nums.size(); i++) {
// used[i - 1] == true,说明同一树枝nums[i - 1]使用过
// used[i - 1] == false,说明同一树层nums[i - 1]使用过
// 如果同一树层nums[i - 1]使用过则直接跳过
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
continue;
}
if (used[i] == false) {
used[i] = true;
path.push_back(nums[i]);
backtracking(nums, used);
path.pop_back();
used[i] = false;
}
}
}
添加一个判断是否用过,和nums[i - 1] == nums[i]
所以,回溯问题上,要抓住关键问题:在单层搜索中,要以一个什么样的策略来进行选择节点,才能满足题目要求,难点1,有重复元素的情况 难点2 :要满足具体任务要求,如IP地址,递增序列等。归根到底需要总结,还需要经验。