LeetCode题解 回溯(四):90 子集II;491 递增子序列;46 全排列;47 全排列II

90 子集II medium

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

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

这道题目和子集的区别在于这道题目中给出的数组中有重复的数字,如果按照此前的方式处理,[1,1,2]中,第一个1和2的组合 = 第二个1和2的组合,但是只能选一个放在最终结果中。

因此,本道题的关键在于去重,至于如何去重,在组合总和II中已经使用过了,一个方法是采用标记数组,另一个方法是根据起始索引值解决,本文中给出基于后者的代码:

vector<vector<int>> res;
vector<int> path;
void recall(vector<int> &nums, int startIndex) {
    
    
    res.push_back(path);

    for (int i = startIndex; i < nums.size(); i++) {
    
    
        if (i > startIndex && nums[i] == nums[i - 1]) continue;
        path.push_back(nums[i]);
        recall(nums, i + 1);
        path.pop_back();
    }
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
    
    
    sort(nums.begin(), nums.end());
    recall(nums, 0);
    return res;
}

我认为,对于同层中的数组去重,还是可以用一个哈希集合来标识数字有没有被使用过,比如1,1,2,当哈希集合中已经有了1之后,就不用再使用第二个1了。


491 递增子序列 medium

给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。示例:

  • 输入: [4, 6, 7, 7]
  • 输出: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]

说明:

  • 给定数组的长度不会超过15。
  • 数组中的整数范围是 [-100,100]。
  • 给定数组中可能包含重复数字,相等的数字应该被视为递增的一种情况。

本题需要注意的问题有二,如何令路径上所有的数字递增,二是因为原数组中有重复的数组,所以要去重。

此前的组合总和II中,通过一个used数组来判断同一层与同一树枝上有没有重复使用的数字,但是这道题中,随想录并没有给出相同的做法。是因为什么呢?

我认为原因在于这道题目的原数组不能排列,且题目中还要求只要是大于等于,都算递增。这就导致,即使,无法判断相邻且相等的数字能不能被重复使用。

比如4,5,3,5,6,4,5,5也是答案之一,但是4,5,6这个答案,仅能出现一个。所以,在同一层中,相同的数字只能使用一个。

我们可以用一个在每一层递归中定义一个哈希表,来判断本层中的元素有没有被重复使用。判断逻辑就是,在每一层的for循环中,如果当前的数小于路径中最后的一个数或者当前的数在本层中已经被使用过了,就直接继续循环,其他操作如正常递归一样。

当路径中数字超过一个,就可以被添加进结果之中。

vector<vector<int>> res;
vector<int> path;

void reback(vector<int>& nums, int startIndex) {
    
    
    if (path.size() > 1) {
    
    
        res.push_back(path);
    }

    unordered_set<int> used;
    for (int i = startIndex; i < nums.size(); i++) {
    
    
        if ((!path.empty() && nums[i] < path.back()) || 
            (used.find(nums[i]) != used.end())) 
            continue;
        used.insert(nums[i]);
        path.push_back(nums[i]);
        reback(nums, i + 1);
        path.pop_back();
    }
}


vector<vector<int>> findSubsequences(vector<int>& nums) {
    
    
    reback(nums, 0);
    return res;
}

46 全排列 medium

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

排列的不同之处,是有顺序的,所以单层遍历的时候,不能从起始索引开始,而是每次都从0开始;为了不重复取元素,所以要用一个used标识数组标记本次深度遍历,哪个数值已经被用过了。

终止条件也很简单,当路径中数字大小等于题目中给出数组的大小时,就可以添加到结果中了。

vector<int> path;
vector<vector<int>> res;

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

    for (int i = 0; i < nums.size(); ++i) {
    
    
        if (used[i] == true) continue;
        used[i] = true;
        path.push_back(nums[i]);
        reback(nums, used);
        used[i] = false;
        path.pop_back();
    }
}

vector<vector<int>> permute(vector<int>& nums) {
    
    
    vector<bool> used(nums.size(), false);
    reback(nums, used);
    return res;
}

47 全排列II medium

想都不用想,这道题与上一题的区别在于数组中能不能有重复的数字

处理起来也很简单,因为是找所有排列,所以原数组的顺序位置并不重要,因此,先对数组进行排序,然后按照此前组合总和II的处理方式一样,通过判断前后两个数字是否相同以及同一层中数字有没有被重复使用过,就可以了。

其他逻辑与上一题是一模一样的,代码如下:

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++) {
    
    
        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;
        }
    }
}
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;
}

猜你喜欢

转载自blog.csdn.net/qq_41205665/article/details/128692100