当前位置:   article > 正文

搜索回溯算法—全排列(leetcode 46)_怎么用搜索和回溯做全排列

怎么用搜索和回溯做全排列

题目描述

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

示例 1:

输入: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]]

提示:

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

算法分析

方法一:回溯

思路和算法

这个问题可以看作有 n个排列成一行的空格,我们需要从左往右依此填入题目给定的 n 个数,每个数只能使用一次。那么很直接的可以想到一种穷举的算法,即从左往右每一个位置都依此尝试填入一个数,看能不能填完这 n个空格,在程序中我们可以用「回溯法」来模拟这个过程。

我们定义递归函数 backtrack(first, output) 表示从左往右填到第 first个位置,当前排列为 output。 那么整个递归函数分为两个情况:

    如果 first==n,说明我们已经填完了 n个位置(注意下标从 0开始),找到了一个可行的解,我们将 output放入答案数组中,递归结束。
    如果 first

使用标记数组来处理填过的数是一个很直观的思路,但是可不可以去掉这个标记数组呢?毕竟标记数组也增加了我们算法的空间复杂度。

答案是可以的,我们可以将题目给定的 nnn 个数的数组 nums划分成左右两个部分,左边的表示已经填过的数,右边表示待填的数,我们在回溯的时候只要动态维护这个数组即可。

具体来说,假设我们已经填到第 first个位置,那么 nums 数组中 [0,first−1]是已填过的数的集合,[first,n−1]是待填的数的集合。我们肯定是尝试用 [first,n−1]里的数去填第 first个数,假设待填的数的下标为 i,那么填完以后我们将第 iii 个数和第 first个数交换,即能使得在填第 first+1个数的时候 nums数组的[0,first]部分为已填过的数,[first+1,n−1]为待填的数,回溯的时候交换回来即能完成撤销操作。

举个简单的例子,假设我们有 [2, 5, 8, 9, 10] 这 5 个数要填入,已经填到第 3 个位置,已经填了 [8,9] 两个数,那么这个数组目前为 [8, 9 | 2, 5, 10] 这样的状态,分隔符区分了左右两个部分。假设这个位置我们要填 10 这个数,为了维护数组,我们将 2 和 10 交换,即能使得数组继续保持分隔符左边的数已经填过,右边的待填 [8, 9, 10 | 2, 5] 。

代码

1、不用其它存储空间标记是否访问

  1. class Solution {
  2. public:
  3. void backtrack(vector<vector<int>> &res, vector<int> &output, int first, int len) {
  4. if(first == len) {
  5. res.push_back(output);
  6. return;
  7. }
  8. for(int i = first; i < len; ++i) {
  9. swap(output[i], output[first]);
  10. backtrack(res, output, first+1, len);
  11. swap(output[i], output[first]);
  12. }
  13. }
  14. vector<vector<int>> permute(vector<int>& nums) {
  15. vector<vector<int>> res;
  16. backtrack(res, nums, 0, nums.size());
  17. return res;
  18. }
  19. };

 2、借用标记数组标记是否访问

  1. class Solution {
  2. public:
  3. void backtrack(vector<int> &nums) {
  4. if(track.size() == n) {
  5. res.push_back(track);
  6. return;
  7. }
  8. for(int i = 0; i < n; ++i) {
  9. if(!status[i]) {
  10. track.push_back(nums[i]);
  11. status[i] = true;
  12. backtrack(nums);
  13. track.pop_back();
  14. status[i] = false;
  15. }
  16. }
  17. }
  18. vector<vector<int>> permute(vector<int>& nums) {
  19. n = nums.size();
  20. status.resize(n, false);
  21. backtrack(nums);
  22. return res;
  23. }
  24. private:
  25. vector<vector<int>> res;
  26. vector<bool> status;
  27. vector<int> track;
  28. int n;
  29. };

时间复杂度分析

    时间复杂度:O(n×n!),其中 n为序列的长度。

    空间复杂度:O(n),其中 n为序列的长度。除答案数组以外,递归函数在递归过程中需要为每一层递归函数分配栈空间,所以这里需要额外的空间且该空间取决于递归的深度,这里可知递归调用深度为 O(n)。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/474569
推荐阅读
相关标签
  

闽ICP备14008679号