力扣46.全排列(回溯法)

题目:

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

示例一:

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

示例二:

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

示例三:

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

提示:

  • 1 <= nums.length <= 6
  • -10 <= nums[i] <= 10
  • nums 中的所有整数 互不相同

思路:

首先还是先来看题目,题目很简单明了,就是找齐全排列的情况,我们可以从提示中得到两点信息。1.nums数组长度最小为1,所以我们不需要对长度进行额外的判断。2.nums数组中的所有整数互不相同,这是很重要的一定。

题目看完了,我们很容易看出用回溯法来做这道题是最合适的,下面是我写的代码。

代码:

class Solution {
public:
    vector> res;
    vector vec;
    set _set;//标记集合

    void BackTracking(vector& nums){
        if(vec.size() == nums.size()){//退出条件
            res.push_back(vec);
            return;
        }
        for(int i = 0; i < nums.size(); i++){//每一层选择
            if(_set.find(nums[i]) == _set.end()){//判断是否可以选择
                vec.push_back(nums[i]);
                _set.insert(nums[i]);
                BackTracking(nums);
                vec.pop_back();//回退到上一个状态
                _set.erase(nums[i]);
            }
        }
    }

    vector> permute(vector& nums) {
        BackTracking(nums);
        return res;
    }
};

代码讲解:

    vector> res;

    vector vec;

    set _set;//标记集合

首先这三个是我们在类中设置的变量,第一个res是一个二维数组,用来保存你最终要返回的答案的。第二个是一个一维数组,是我们用来存储每一个全排列的情况来使用的(这里我们使用vector数组的大容器与小容器的关系,我们直接将小容器(vec)装入大容器(res)中,就完成了二维数组的值的添加)。第三个是一个标记集合,标记集合我们在学习图的时候用到的非常多,这里的含义和在图中的含义是一样的,就是用力啊标记这个树是否已经加入到vec中。

    vector> permute(vector& nums) {

        BackTracking(nums);

        return res;

    }

这就是主函数,没什么好说的,先调用了我们写的回溯函数,然后返回res数组。

 

    void BackTracking(vector& nums){

        if(vec.size() == nums.size()){//退出条件

            res.push_back(vec);

            return;

        }

        for(int i = 0; i < nums.size(); i++){//每一层选择

            if(_set.find(nums[i]) == _set.end()){//判断是否可以选择

                vec.push_back(nums[i]);

                _set.insert(nums[i]);

                BackTracking(nums);

                vec.pop_back();//回退到上一个状态

                _set.erase(nums[i]);

            }

        }

    }

接下来就是我们回溯的主要代码,首先我们先来讲一下代码,具体的流程我会放在下面。

首先先进来的if判断,用处就是回退的,和递归当中的一样,只不过这时我们完成小容器装入到大容器中这一步。

下面的for循环就是我们在写回溯时要用到的选择系统,我们如何去完成每一次选哪个数字技术通过for循环来完成的。

进入for循环当中,首先先从标记集合中判断当前数字是否已经放入vec中(学过图的同学应该对这个印象会很深),若没有放过,则进入if。

进入if,首先我们讲当前数字放入vec和标记集合当中,然后再次调用回溯函数 ,这里大体上和递归时一模一样的,区别就在于当回退到这里再向下运行函数时,我们完成的状态的回退,就是将vec和标记集合中的当前数字弹出,在弹出之后for循环i++,当前数字改变。

接下来我来给大家描述一下整个流程

这里我们拿示例一【1,2,3】来给大家讲

首先 permute函数调用了我们写的回溯函数,此时推出条件不成立,继续进行下面的代码,进入for循环当中,我们将1放入vec数组和_set标记集合中,然后再次调用回溯函数,(之后的回溯函数我们只说推出条件成立时的情况,不成立我们将不会赘述)进入for循环,此时i=0,nums[i]=1,标记集合当中查到了1已经存在了,所以i++,nums[i]=2,我们将2放入vec和标记集合中,再次调用回溯函数,1和2都在标记集合中存在了,所以3加入到vec和标记集合当中,再次调用回溯函数,此时满足退出条件,【1,2,3】的集合被装入到res中,回到上一层回溯

                vec.pop_back();//回退到上一个状态

                _set.erase(nums[i]);

我们使用这两条语句完成了状态的回退,将3从vec和标记集合中弹出,for循环结束,回退到上一层回溯,上一层回溯又将2从vec和标记集合中弹出,for循环的i++,此时nums[i]=3,将3加入到vec和标记集合当中,然后再次调用回溯函数,进入for循环发现1在标记集合中,所以将2加入vec和标记集合中,然后再次调用回溯函数,此时满足退出条件,【1,3,2】被装入到res当中,然后回退到上一层回溯,将2从vec和标记集合中弹出,for循环i++,nums[i] = 3,发现3已经在标记集合中了,for循环结束,回退到上一层循环,将3从vec和标记集合中弹出,for循环结束,将1从vec和标记集合中弹出,for循环i++,nums[i]=2,接下来就是和之前流程大差不差了,之不过是以2卡头了。 

你可能感兴趣的:(leetcode,java,算法)