算法训练1-day22-回溯

26 阅读3分钟

回溯法来了

  1. 491. 非递减子序列

要处理重复的情况,比如[4,6,7,7],当选了[4,6],再选了一个7后,最后的7就不能再选了;因为题目要求子序列,因此不能排序,使用使用哈希表来记录每一次回溯过程中都加入了哪些元素;注意判断条件:先判断path.empty()然后或是上nums[i] >= path[path.size() - 1],当这两个其中之一满足时,代表nums[i]满足条件,因此继续判断后续的插入是否成功 AC代码:

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        backtracing(nums, 0);
        return ans;
    }
    void backtracing(vector<int>& nums, int startIndex) {
        if (path.size() >= 2) {
            ans.push_back(path);
        }

        unordered_set<int> used;
        for (int i = startIndex; i < nums.size(); ++i) {
            if ((path.empty() || nums[i] >= path[path.size() - 1]) &&
                (used.insert(nums[i])).second) {
                path.push_back(nums[i]);
                backtracing(nums, i + 1);
                path.pop_back();
            }
        }
    }
};
  1. 46. 全排列

要求排列,因此我们必须要记录之前都选了哪些值,遍历的时候也得从0开始,通过判断当前值是否在之前已经加入过记录数组,来过滤出还未选择的值 AC代码:

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    vector<vector<int>> permute(vector<int>& nums) {
        vector<bool> used(nums.size());
        backtracing(nums, used);
        return ans;
    }

    void backtracing(vector<int>& nums, vector<bool>& used) {
        if (path.size() == nums.size()) {
            ans.push_back(path);
            return;
        }

        for (int i = 0; i < nums.size(); ++i) {
            if (!used[i]) {
                used[i] = true;
                path.push_back(nums[i]);
                backtracing(nums, used);
                path.pop_back();
                used[i] = false;
            }
        }
    }
};
  1. 47. 全排列 II 和上一题类似,只不过不能有重复的排列,因此需要去重:先排序,然后在遍历时遇到重复值跳过就好 一般来说:组合问题和排列问题是在树形结构的叶子节点上收集结果,而子集问题就是取树上所有节点的结果

代码如下:

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<bool> used(nums.size());
        sort(nums.begin(), nums.end());
        backtracing(nums, used);
        return ans;
    }

    void backtracing(vector<int>& nums, vector<bool>& used) {
        if (path.size() == nums.size()) {
            ans.push_back(path);
            return;
        }

        for (int i = 0; i < nums.size(); ++i) {
            if (!used[i]) {
                used[i] = true;
                path.push_back(nums[i]);
                backtracing(nums, used);
                path.pop_back();
                used[i] = false;
				
				// 去重,当这一层已经选了一个值nums[i],
				// 那么后面所有和nums[i]相同的值就都不用选了
				// 因为,假设我们已经有了排列[a,b],
				// 那么以[a,b,nums[i]]为开头的排列结果我们就已经得到了
				// 后面再在[a,b]的基础上加入nums[i]就会加入重复结果了
                while(i < nums.size() - 1 && nums[i + 1] == nums[i]){
                    ++i;
                }
            }
        }
    }
};

// 直接复用used数组来去重的写法
class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking (vector<int>& nums, vector<bool>& used) {
        // 此时说明找到了一组
        if (path.size() == nums.size()) {
            result.push_back(path);
            return;
        }
        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;
            }
        }
    }
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        result.clear();
        path.clear();
        sort(nums.begin(), nums.end()); // 排序
        vector<bool> used(nums.size(), false);
        backtracking(nums, used);
        return result;
    }
};

// 时间复杂度: 最差情况所有元素都是唯一的。复杂度和全排列1都是 O(n! * n) 对于 n 个元素一共有 n! 中排列方案。而对于每一个答案,我们需要 O(n) 去复制最终放到 result 数组
// 空间复杂度: O(n) 回溯树的深度取决于我们有多少个元素