一刷80-回溯res+path-46全排列(m)(剑指 Offer II 083. 没有重复元素集合的全排列)

题目:
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
--------------
示例:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例 2:

输入:nums = [0,1]
输出:[[0,1],[1,0]]
示例 3:

输入:nums = [1]
输出:[[1]]
--------------------
思考:
相信这个排列问题就算是让你用for循环暴力把结果搜索出来,这个暴力也不是很好写。
我以[1,2,3]为例,抽象成树形结构如下:

在这里插入图片描述

回溯三部曲:
递归函数参数
首先排列是有序的,也就是说 [1,2][2,1] 是两个集合,这和之前分析的子集以及组合所不同的地方。

可以看出元素1[1,2]中已经使用过了,但是在[2,1]中还要在使用一次1,所以处理排列问题就不用使用startIndex了。

但排列问题需要一个used数组,标记已经选择的元素,如上图橘黄色部分所示。

代码如下:
vector<vector<int>> result;
vector<int> path;
void backtracking (vector<int>& nums, vector<bool>& used)

递归终止条件
可以看出叶子节点,就是收割结果的地方。
那么什么时候,算是到达叶子节点呢?
当收集元素的数组path的大小达到和nums数组一样大的时候,说明找到了一个全排列,也表示到达了叶子节点。

代码如下:
// 此时说明找到了一组
if (path.size() == nums.size()) {
    result.push_back(path);
    return;
}
单层搜索的逻辑
这里和77.组合问题 、131.切割问题 和78.子集问题 最大的不同就是for循环里不用startIndex了。

因为排列问题,每次都要从头开始搜索,例如元素1[1,2]中已经使用过了,但是在[2,1]中还要再使用一次1。

大家此时可以感受出排列问题的不同:

每层都是从0开始搜索而不是startIndex
需要used数组记录path里都放了哪些元素了
排列问题是回溯算法解决的经典题目
-----------------------
代码://通过判断path中是否存在数字,排除已经选择的数字
class Solution {//组合用startIndex  排列用i每次从0开始
    List<List<Integer>> res = new ArrayList<>();//结果集res
    LinkedList<Integer> path = new LinkedList<>();//路径集path
    public List<List<Integer>> permute(int[] nums) {
        if (nums.length == 0) return new ArrayList<>();
        backTracking(nums);
        return res;
    }
    private void backTracking(int[] nums) {
        if (nums.length == path.size()) {//递归终止条件
            res.add(new ArrayList<>(path));
            return;
        }
        for (int i = 0; i < nums.length; i++) {
            if (path.contains(nums[i])) continue;//若path中已有,则跳过
            path.add(nums[i]);
            backTracking(nums);
            path.removeLast();
        }
    }
}

力扣链接

当需要解决带重复元素全排列问题时,可以使用回溯法来实现。具体实现方法如下: 1. 首先,将输入的数组排序,这样相同的元素就会排在一起。 2. 使用一个布尔型数组来记录每个元素是否被使用过。 3. 从数组的第一个元素开始,枚举每个元素作为排列中的第一个元素。 4. 如果当前元素和前一个元素相同,并且前一个元素没有被使用过,则跳过当前元素,因为它已经在前一个元素时被考虑过了。 5. 如果当前元素没有被使用过,则将其加入到排列中,并标记为已使用。 6. 如果排列中的元素个数等于原数组的长度,说明找到了一个排列,输出它。 7. 否则,递归处理剩余的元素。 8. 回溯时,将当前元素从排列中删除,并将其标记为未使用。 下面是示例代码: ``` #include <iostream> #include <algorithm> #include <vector> using namespace std; void backtrack(vector<int>& nums, vector<int>& permutation, vector<bool>& used, vector<vector<int>>& res) { if (permutation.size() == nums.size()) { res.push_back(permutation); return; } for (int i = 0; i < nums.size(); i++) { if (used[i] || (i > 0 && nums[i] == nums[i - 1] && !used[i - 1])) { continue; } used[i] = true; permutation.push_back(nums[i]); backtrack(nums, permutation, used, res); permutation.pop_back(); used[i] = false; } } vector<vector<int>> permuteUnique(vector<int>& nums) { vector<vector<int>> res; vector<int> permutation; vector<bool> used(nums.size(), false); sort(nums.begin(), nums.end()); backtrack(nums, permutation, used, res); return res; } int main() { vector<int> nums = {1, 2, 2}; vector<vector<int>> res = permuteUnique(nums); for (auto& permutation : res) { for (auto& num : permutation) { cout << num << " "; } cout << endl; } return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值