回溯算法的去重问题

概述

在利用回溯算法去求子集、排列、组合等问题时,所给数组中如果包含重复元素,需要进行去重操作。常用的去重方法是使用used数组或集合。

对于上述子集、排列、组合等问题的求解方法是将数组转化为的形式,利用递归和回溯的方法进行求解。去重操作主要处理的是在同一层的数据的重复操作。

例如:力扣90题子集问题,将其转换为树的形式以及去重操作示意图如下

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的 

子集

(幂集)。

解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

示例 1:

输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]

示例 2:

输入:nums = [0]
输出:[[],[0]]

used方法为设置一个bool类型的数组,数组中元素如果为false则表示在同一层使用过,如果为true表示在同一树枝(纵向)使用过。如果用set集合的方式,则如果索引到的元素在集合出现过,则表示在同一层使用过。两者不同的是used数组在函数参数定义及函数外定义,所占空间内存小,为O(n),set集合在函数中for循环外定义,每次递归需要再次展开内存,所占空间大,为O(n*2)。

例题

集合

数组形式,注意要进行排序

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int startIndex, vector<bool>& used) {
        result.push_back(path);
        for (int i = startIndex; i < nums.size(); i++) {
            // used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
            // used[i - 1] == false,说明同一树层candidates[i - 1]使用过
            // 而我们要对同一树层使用过的元素进行跳过
            if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
                continue;
            }
            path.push_back(nums[i]);
            used[i] = true;
            backtracking(nums, i + 1, used);
            used[i] = false;
            path.pop_back();
        }
    }

public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        result.clear();
        path.clear();
        vector<bool> used(nums.size(), false);
        sort(nums.begin(), nums.end()); // 去重需要排序
        backtracking(nums, 0, used);
        return result;
    }
};

集合形式

void trversal(vector<int>&nums,int index)
    { 
        result.push_back(path);
        if(index>=nums.size())
            return;
        unordered_set<int>uset;
        for(int i=index;i<nums.size();i++)
        {
            if(uset.find(nums[i])!=uset.end())//去重,同一层
                continue;
            path.push_back(nums[i]);
            uset.insert(nums[i]);
            trversal(nums,i+1);
            path.pop_back(); 
        }
    }
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        result.clear();
        path.clear();
       // vector<bool>used(nums.size(),false);
        sort(nums.begin(),nums.end());
        trversal(nums,0);
        return result;
    }
};

全排列

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。

示例 1:

  • 输入:nums = [1,1,2]
  • 输出: [[1,1,2], [1,2,1], [2,1,1]]

示例 2:

  • 输入:nums = [1,2,3]
  • 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

提示:

  • 1 <= nums.length <= 8
  • -10 <= nums[i] <= 10

对于排列问题,和集合问题的不同点在于for循环的开始位置不同,集合问题为当前元素的下一个元素的位置,而排列问题一直为数组的起始位置,需要注意的是要加上元素是否使用的判断。

代码如下

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) {//未在树枝使用,可以加入到path数组中
                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) 回溯树的深度取决于我们有多少个元素

相关推荐

  1. 回溯需要先排序

    2024-07-19 00:58:03       54 阅读
  2. 问题:数组对象

    2024-07-19 00:58:03       63 阅读
  3. 实现数组方式

    2024-07-19 00:58:03       49 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-19 00:58:03       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-19 00:58:03       72 阅读
  3. 在Django里面运行非项目文件

    2024-07-19 00:58:03       58 阅读
  4. Python语言-面向对象

    2024-07-19 00:58:03       69 阅读

热门阅读

  1. GNN论文粗读

    2024-07-19 00:58:03       23 阅读
  2. 介绍一些编程语言— Mojo 语言

    2024-07-19 00:58:03       21 阅读
  3. RNN与CNN:昔日辉煌与今日应用的深度透视

    2024-07-19 00:58:03       20 阅读
  4. nvide shortcuts table

    2024-07-19 00:58:03       23 阅读
  5. style-components使用手册

    2024-07-19 00:58:03       19 阅读
  6. MySQL中的幻读究竟是怎么回事?

    2024-07-19 00:58:03       18 阅读
  7. 大语言模型-基础及拓展应用

    2024-07-19 00:58:03       22 阅读