【Leetcode】46. Permutations

题目地址:

https://leetcode.com/problems/permutations/

给定一个长 n n n数组 A A A,求其全排列。题目保证 A A A中无重复数字。

法1:用经典的回溯算法,对应着多叉树的深搜。首先想象一下我们要深搜的树,可以画成下面的样子:
在这里插入图片描述
先设一个list,存储遍历这棵树的时候,当前走到的节点。每当遍历到叶子节点,也就是当前的list已经有了 3 3 3个数的时候,就要将这个list加入最后的结果集,然后回溯回去。所以v.size() == A.size()就是递归的出口。
一开始我们站在树根,也就对应着什么也没选。接下来开始做决策。在树的第一层,我们有三个决策,对应着第一个数字选 1 1 1 2 2 2 3 3 3,一旦做完第一个决策,我们走到第二层的时候,继续做决策,我们发现,只要新加入的数字不在当前的list里,就可以将其加入。所以决策条件就是,新待加入的数字不在当前的list里。决策完了以后就递归遍历下一层子树。之后再撤销选择。代码如下:

class Solution {
 public:
  vector<vector<int>> permute(vector<int>& A) {
    vector<vector<int>> res;
    vector<int> v;
    int vis = 0;
    dfs(v, A, vis, res);
    return res;
  }

  void dfs(vector<int>& v, vector<int>& A, int& vis, vector<vector<int>>& res) {
    if (v.size() == A.size()) {
      res.push_back(v);
      return;
    }

    for (int i = 0; i < A.size(); i++) {
      if (vis >> i & 1) continue;
      vis |= 1 << i;
      v.push_back(A[i]);
      dfs(v, A, vis, res);
      v.pop_back();
      vis -= 1 << i;
    }
  }
};

空间复杂度为 O ( n ∗ n ! ) O(n*n!) O(nn!),因为 n n n元排列有 n ! n! n!个,每个排列需要存储 n n n个数字。时间复杂度需要用递推方程计算,主要是要算helper方法的复杂度。设nums的长度为 n n n,cur的长度为 k k k时,helper花费的时间是 T ( n , k ) T(n,k) T(n,k),则:
T ( n , k ) = ( n − k ) T ( n , k + 1 ) T ( n , n ) = n T(n,k)=(n-k)T(n,k+1)\\T(n,n)=n T(n,k)=(nk)T(n,k+1)T(n,n)=n有: T ( n , 0 ) = n T ( n , 1 ) = . . . = n ! T ( n , n ) = n ∗ n ! T(n,0)=nT(n,1)=...=n!T(n,n)=n*n! T(n,0)=nT(n,1)=...=n!T(n,n)=nn!所以时间复杂度是 O ( n ! n ) O(n!n) O(n!n)

算法正确性证明:

首先证明任意一个排列都会被加入res。我们只需要根据某个具体的排列,来关注那个选择路径就可以了。比如对于排列 123... n 123...n 123...n,那么可以考虑第 0 0 0层helper的for循环在i=0时将 1 1 1加入了cur,接着开始递归下一层helper,在第 1 1 1层helper的for循环中在 i = 2 i=2 i=2时将 2 2 2加入了cur,直到第 n − 1 n-1 n1层helper的for循环在 i = n − 1 i=n-1 i=n1时将 n n n加入了cur,而此时,cur的长度等于 n n n,被加入了res。所以这一条证明完成。

其次证明任意一个排列只会被加入res一次。这一点是显然的,因为每一层for循环在遍历某个位置 i i i的时候只会pass一次。

法2: n n n元全排列也可以这么生成:从 12... n 12...n 12...n这个排列出发,我们先挑选谁成为最后一个数字,然后将 n n n与其进行交换(可以与自己交换),然后类似地递归挑选倒数第二个数字,并交换。每次挑选倒数第 k k k个数字的时候,只从 1 , . . . , n − k + 1 1,...,n-k+1 1,...,nk+1中挑选,以避免重复。当挑选第 1 1 1个数字的时候,情况只有一种可能了,这就是递归出口。代码如下:

class Solution {
 public:
  vector<vector<int>> permute(vector<int>& nums) {
    vector<vector<int>> res;
    dfs(0, nums, res);
    return res;
  }

  void dfs(int u, vector<int>& v, vector<vector<int>>& res) {
    if (u == v.size()) {
      res.push_back(v);
      return;
    }

    for (int i = u; i < v.size(); i++) {
      swap(v[i], v[u]);
      dfs(u + 1, v, res);
      swap(v[i], v[u]);
    }
  }
};

空间复杂度 O ( n ∗ n ! ) O(n*n!) O(nn!),有 n ! n! n!个子集,并且每个都要copy一份加入最终结果。设list长度为 n n n,pos等于 k − 1 k-1 k1,时间复杂度有递推方程 T ( k ) = n + k T ( k − 1 ) T ( 0 ) = n T(k)=n+kT(k-1)\\T(0)=n T(k)=n+kT(k1)T(0)=n所以有: T ( k ) − k T ( k − 1 ) = n k T ( k − 1 ) − k ( k − 1 ) T ( k − 2 ) = n k . . . k ! T ( 1 ) − k ! T ( 0 ) = n k ! T(k)-kT(k-1)=n\\kT(k-1)-k(k-1)T(k-2)=nk\\...\\k!T(1)-k!T(0)=nk! T(k)kT(k1)=nkT(k1)k(k1)T(k2)=nk...k!T(1)k!T(0)=nk!累加起来得: T ( n ) = n ! n + n ! n ( ∑ i = 0 n 1 i ! ) = O ( n ∗ n ! ) T(n)=n!n+n!n(\sum_{i=0}^{n}\frac{1}{i!})=O(n*n!) T(n)=n!n+n!n(i=0ni!1)=O(nn!)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值