回溯算法中进行选择待选项?

76 阅读1分钟
  • 简单的情况

组合问题,待选区间为[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地址,递增序列等。归根到底需要总结,还需要经验。