[每月一题] 全排列的生成方法以及扩展问题

常见的题,给一个数组返回所有排列。

vector<vector<int>> permutation(vector<int> &nums);

有不少思路:

1. 减治

首先一个数的所有排列只有一种,就是本身。这看起来就像一个递归结束的条件。

所以很容易想到递归的过程:

a.将第一个元素拿出来,求后面元素的全排列,然后把第一个元素放在第一个位置;依次让所有元素都坐一遍第一把交椅。(这个非递归没想到好的方法,写得很烂)

b.或者,将第一个元素拿出来,求后面元素的全排列,然后对每一个排列都把第一个元素插在中间的n个位置,这样一共是n*(n-1)!种排列同样达到目的。

2.DP

c.或者,反过来,将第一个元素拿出来全排列,然后第二个元素依次插在空位得到新的全排列,然后第三个在之前的每个排列插入每个空位。


当然只谈思路不给代码就是耍流氓。为了不影响观赏性,代码在文末附上,其中一些包括非递归实现。


看起来这个题也不太难。不过它有许多变体,可以讨论一下:

3.把全排列改成不重复全排列,也就是如果有重复的数,那么完全相同的排列不能重复计算。

例如1,1,1就只有一种,1,1,2则有3种。

基本思路和上面类似,但是要考虑怎么处理相同元素。

依次看一下上面的解法:

a,把两个相同元素分别放在第一个位置时,其余n-1个元素是相同的,导致产生相同的n-1全排列,最后得到重复的结果。

而只要第一个位置不是相同的元素,那么它们所形成的全排列就一定不相同。

按照这个思路,可以有两种做法,一种是每次放第一个元素的时候都记录下来用过哪些;一种是先做排序然后避免将重复元素放在首位。

b与c是相似的。在得到n-1个元素的不重复的全排列之后,想要将第n个元素插入每一个排列中,不仅排列本身会产生重复,而且排列之间还会产生重复。玩不下去了。

最终居然只有一种解法可行,不服!

继续尝试寻找,发现了如下方法:

STL:后来才知道有这么一个函数:bool next_permutation( iterator start, iterator end );

STL的强大自不必说,它会生成下一个比当前排列大1的排列,所以不会有重复的排列。当然我们也可以自己实现这个next_permutation。


4.把所有元素的全排列改成其中k个元素的全排列。

这个还是比较简单的,只需要在原来的方法中添加一个限制条件即可,abc都可以实现。

不过我们还是期望能想出别的方法,比如先找出k个数的所有组合,然后再对这些组合进行全排列,看起来也不错。

找出k个数的组合,由于有上面的过程,所以应该是更简单的一件事,因此也可以结合STL函数来实现,简直是很high。

然后,还看到一种使用BIT MANIPULATION来做的,bit manipulation真是一种神奇的方法,足够单独写一篇了:

求( 1 ...m)中,n个数的组合
本程序的思路是开一个数组,其下标表示 1 到m个数,数组元素的值为 1 表示其下标代表的数被选中,为 0 则没选中。     
    首先初始化,将数组前n个元素置 1 ,表示第一个组合为前n个数。     
    然后从左到右扫描数组元素值的“ 10 ”组合,找到第一个“ 10 ”组合后将其变为“ 01 ”组合,
    同时将其左边的所有“ 1 ”全部移动到数组的最左端。     
    当第一个“ 1 ”移动到数组的m-n的位置,即n个“ 1 ”全部移动到最右端时,就得到了最后一个组合。   
   
    例如求 5 中选 3 的组合:     
    1     1     1     0     0     //1,2,3  
    1     1     0     1     0     //1,2,4 
    1     0     1     1     0     //1,3,4 
    0     1     1     1     0     //2,3,4  
    1     1     0     0     1     //1,2,5    
    1     0     1     0     1     //1,3,5     
    0     1     1     0     1     //2,3,5    
    1     0     0     1     1     //1,4,5     
    0     1     0     1     1     //2,4,5     
    0     0     1     1     1     //3,4,5 
其原理也是DP的思路,即:C(n, k) = a[k] + C(k - 1, k - 1), a[k+1] + C(k, k - 1), a[k + 2] + C(k + 1, k - 1), ... , a[n - 1] + C(n - 1, k - 1)


5.生成下一个排列

刚才多次谈到的next_permutation函数,亲手实现一下吧!

先观察一下规律:一个排列的下一个字典序排列,直觉上来说不用改变太多。比如1234的下一个是1243,再下一个是1324,再下一个是1342。

看起来很有规律。也就是前面几位不需要变化,只需要对后面的数字改变即可。那么怎么判断需要改变哪一位呢?当后面的数字没有下一个排列的时候,就需要往前看一位了。什么时候会没有下一个排列呢?从大到小排序的时候,就没有下一个排列了。

这样思路清晰一些了。就是要从后往前找到第一个升序的相邻数字。比如1234中的34,1243中的24。

然后,对后面这ai,ai+1..ai+k个数字怎么产生下一个排列呢?显然ai是要改变的,而且要换成一个比ai大的数,而且要尽量小,否则就可能是下下下个排列了。所以找到一个刚好比ai大的数与ai交换。可以肯定替换完之后比原来的排列要大,然后剩下的k个数怎么放呢?当然是从小到大排列,这样才刚好是原来排列的下一个排列。这时候注意到,交换之后剩下的k个数其实还是从大到小排列的,因此只需要逆转一下即可。


6.生成第k个排列

给定一些数,求它们的第k个排列。

有了上一步基础,我们可以直接遍历k次嘛,平推过去!

然并卵,这种类型的重用是不会帮助我们解决问题的。老老实实回到找规律的环节。其实我们知道n个数的全排列一共是n!种,那么给定n个数,第一个数如果是最小值,剩下的数会组成(n-1)!种排列,因此如果k在0~(n-1)!这个范围,第一个数就肯定是最小值咯!同理,如果是在(n-1)!~2*(n-1)!,就是第二大的数等等。继续往后,第二个数、第三个数。。。也可以类似地判断。算算时间复杂度吧。每个数要推断一次,一共n次;每次推断需要n次判断,所以总时间复杂度是O(n^2)。这看起来有也不小啊。不过想想k的规模可是n!,瞬间感觉好多了。。


就先到这儿吧~ 下面是代码时间:



// 1.a take any element as first then get permutations of the rest
// recursive
void permutations_a_recur(vector<int> &nums, int start, vector<vector<int>> &res) {
  int n = nums.size();
  if (start >= n) {
    res.push_back(nums); // copy nums into res
    return;
  }
  for (int i = start; i < n; ++i) {
    std::swap(nums[start], nums[i]);
    permutations_a_recur(nums, start + 1, res);
    std::swap(nums[start], nums[i]);
  }
}
vector<vector<int>> permutations_a_recur(vector<int> &nums) {
  vector<vector<int>> res;
  if (!nums.empty()) permutations_a_recur(nums, 0, res);
  return res;
}
// iterative, which is hard to understand and not effecient
vector<vector<int>> permutations_a_iter(vector<int> &nums) {
  int n = nums.size();
  vector<vector<int>> res;
  if (n == 0) return res;
  stack<pair<int, int>> st; // store exchange status
  pair<int, int> status;
  bool restore = false; // restore flag
  int i = 0, j = 0;
  while (!restore || !st.empty()) {
    if (restore) { // if can not go further exchange
      status = st.top();
      st.pop();
      i = status.first, j = status.second;
      std::swap(nums[i], nums[j]); // restore previous exchange
      if (j == n - 1) continue; // check if level i can go further
      restore = false;
      ++j;
    } else {
      if (i == n - 1 && j == n - 1) { // come to an permutation
        res.push_back(nums);
        restore = true; // no further exchange can be perform, restore
      } else {
        std::swap(nums[i], nums[j]);
        st.push(make_pair(i, j)); // save exchange status for later restore
        j = ++i; // move to level i + 1
      }
    }
  }
  return res;
}
// another iterative implementation, faster than pre but still slower than recursive, more space complexity
vector<vector<int>> permutations_a_iter2(vector<int> &nums) {
  vector<vector<int>> res;
  int n = nums.size();
  if (n == 0) return res;
  queue<pair<vector<int>, int>> st;
  st.push(make_pair(nums, 0));
  pair<vector<int>, int> p;
  while (true) {
    p = st.front();
    if (p.second == n) break;
    st.pop();
    int curr = p.second;
    // exchange first with every element behind
    for (int i = curr; i < n; ++i) {
      vector<int> tmp = p.first;
      std::swap(tmp[curr], tmp[i]);
      st.push(make_pair(tmp, curr + 1));
      if (curr == n - 1) res.push_back(tmp);
    }
  }
  return res;
}


// 1.b get permutations of last n - 1 elements, and insert first to each
// recursive, a little slower than a
void permutations_b_recur(vector<int> &nums, int start, vector<vector<int>> &res) {
  int n = nums.size();
  if (start == n - 1) {
    res.push_back(vector<int>(1, nums[start]));
    return;
  }
  permutations_b_recur(nums, start + 1, res);
  int sz = res.size();
  for (int i = 0; i < sz; ++i) {
    for (int j = 1; j <= res[i].size(); ++j) {
      res.push_back(res[i]);
      vector<int> &tmp = res.back();
      tmp.insert(tmp.begin() + j, nums[start]); // insert in vector is not efficient
    }
    res[i].insert(res[i].begin(), nums[start]);
  }
}
vector<vector<int>> permutations_b_recur(vector<int> &nums) {
  vector<vector<int>> res;
  if (!nums.empty()) permutations_b_recur(nums, 0, res);
  return res;
}
// iterative
vector<vector<int>> permutations_b_iter(vector<int> &nums) {
  vector<vector<int>> res;
  int n = nums.size();
  if (n == 0) return res;
  res.push_back(vector<int>());
  while (n--) {
    int sz = res.size();
    for (int i = 0; i < sz; ++i) {
      for (int j = 1; j <= res[i].size(); ++j) {
        res.push_back(res[i]);
        vector<int> &tmp = res.back();
        tmp.insert(tmp.begin() + j, nums[n]);
      }
      res[i].insert(res[i].begin(), nums[n]);
    }
  }
  return res;
}


// 2.c almost same as 1.b, skip over


// 3.a distinct permutation, each element swap with first
// recursive
void distinct_permutations_a_recur(vector<int> &nums, int start, vector<vector<int>> &res) {
  int n = nums.size();
  if (start >= n) {
    res.push_back(nums); return;
  }
  unordered_set<int> st;
  for (int i = start; i < n; ++i) {
    if (st.find(nums[i]) == st.end()) {
      st.insert(nums[i]);
      std::swap(nums[start], nums[i]);
      distinct_permutations_a_recur(nums, start + 1, res);
      std::swap(nums[start], nums[i]);
    }
  }
}
vector<vector<int>> distinct_permutations_a_recur(vector<int> &nums) {
  vector<vector<int>> res;
  if (!nums.empty()) distinct_permutations_a_recur(nums, 0, res);
  return res;
}
// another recursive implementation
void distinct_permutations_a_recur2(vector<int> &nums, int start, vector<vector<int>> &res) {
  int n = nums.size();
  if (start >= n) {
    res.push_back(nums); return;
  }
  for (int i = start; i < n; ++i) {
    while (i < n - 1 && nums[i] == nums[i + 1]) ++i;
    std::swap(nums[start], nums[i]);
    distinct_permutations_a_recur(nums, start + 1, res);
    std::swap(nums[start], nums[i]);
  }
}
vector<vector<int>> distinct_permutations_a_recur2(vector<int> &nums) {
  vector<vector<int>> res;
  if (!nums.empty()) {
    sort(nums.begin(), nums.end());
    distinct_permutations_a_recur(nums, 0, res);
  }
  return res;
}
// iterative
vector<vector<int>> distinct_permutations_a_iter(vector<int> &nums) {
  int n = nums.size();
  vector<vector<int>> res;
  if (n == 0) return res;
  sort(nums.begin(), nums.end());
  stack<pair<int, int>> st; // store exchange status
  pair<int, int> status;
  bool restore = false; // restore flag
  int i = 0, j = 0;
  while (!restore || !st.empty()) {
    if (restore) { // if can not go further exchange
      status = st.top();
      st.pop();
      i = status.first, j = status.second;
      std::swap(nums[i], nums[j]); // restore previous exchange
      while (j < n - 1 && nums[j] == nums[j + 1]) ++j;
      if (j >= n - 1) continue; // check if level i can go further
      restore = false;
      ++j;
    }
    else {
      if (i == n - 1 && j == n - 1) { // come to an permutation
        res.push_back(nums);
        restore = true; // no further exchange can be perform, restore
      }
      else {
        std::swap(nums[i], nums[j]);
        st.push(make_pair(i, j)); // save exchange status for later restore
        j = ++i; // move to level i + 1
      }
    }
  }
  return res;
}
// 3.stl use stl in distinct permutations
vector<vector<int>> distinct_permutations_stl(vector<int> &nums) {
  vector<vector<int>> res;
  if (nums.empty()) return res;
  sort(nums.begin(), nums.end());
  res.push_back(nums);
  while (next_permutation(nums.begin(), nums.end())) res.push_back(nums);
  return res;
}


// 4. get permutations of k elements in array of n
void permutations_k(
    vector<int> &nums, 
    int start, 
    int k, 
    vector<int> &pre, 
    vector<vector<int>> &res
  ) {
  if (k == 0) {
    res.push_back(pre);
    return;
  }
  int n = nums.size();
  for (int i = start; i < n; ++i) {
    std::swap(nums[i], nums[start]);
    pre.push_back(nums[start]);
    permutations_k(nums, start + 1, k - 1, pre, res);
    pre.pop_back();
    std::swap(nums[i], nums[start]);
  }
}
vector<vector<int>> permutations_k(vector<int> &nums, int k) {
  int n = nums.size();
  if (k > n) k = n;
  vector<vector<int>> res;
  vector<int> pre;
  if (n > 0) permutations_k(nums, 0, k, pre, res);
  return res;
}
// get C(n, k) first, then get all permutations
// get collection
// recursive
void collections_k_recur(
    vector<int> &nums,
    int start,
    int k,
    vector<int> &pre,
    vector<vector<int>> &res
  ) {
  if (k == 0) {
    res.push_back(pre);
    return;
  }
  int n = nums.size();
  if (start >= n) return;
  pre.push_back(nums[start]);
  collections_k_recur(nums, start + 1, k - 1, pre, res);
  pre.pop_back();
  collections_k_recur(nums, start + 1, k, pre, res);
}
vector<vector<int>> collections_k_recur(vector<int> &nums, int k) {
  int n = nums.size();
  if (k > n) k = n;
  vector<vector<int>> res;
  vector<int> pre;
  if (n > 0) collections_k_recur(nums, 0, k, pre, res);
  return res;
}
// iterative
vector<vector<int>> collection_k_iter(vector<int> &nums, int k) {
  int n = nums.size();
  if (k > n) k = n;
  vector<vector<int>> res;
  if (n == 0) return res;
  queue<pair<vector<int>, int>> st;
  st.push(make_pair(vector<int>(), 0));
  pair<vector<int>, int> p;
  while (!st.empty()) {
    p = st.front();
    st.pop();
    if (p.first.size() == k) {
      res.push_back(p.first);
      continue;
    }
    if (p.second >= n) continue;
    st.push(make_pair(p.first, p.second + 1));
    p.first.push_back(nums[p.second]);
    st.push(make_pair(p.first, p.second + 1));
  }
  return res;
}


// 5. next permutation
bool next_p(vector<int> &nums) {
  if (nums.empty()) return false;
  vector<int> res;
  int n = nums.size(), change_index = n - 1;
  while (change_index > 0 && nums[change_index - 1] >= nums[change_index]) {
    --change_index;
  }
  if (change_index-- == 0) return false;
  // binary search for the smallest one larger than change_index - 1
  int low = change_index + 1, high = n, mid, value = nums[change_index];
  while (low < high) {
    mid = low + ((high - low) >> 1);
    if (nums[mid] > value &&
        (mid == n - 1 || nums[mid + 1] <= value)) {
      break;
    }
    if (nums[mid] <= value) high = mid;
    else low = mid + 1;
  }
  std::swap(nums[change_index], nums[mid]);
  // reverse nums[change_index to n - 1]
  low = change_index + 1, high = n - 1;
  while (low < high) {
    std::swap(nums[low++], nums[high--]);
  }
  return true;
}


// 6. kth permutation
void kth_permutation(vector<int> &nums, int k) {
  int n = nums.size();
  if (n < 2) return;
  vector<int> levels(2, 1);
  for (int i = 2; i <= n; ++i) {
    levels.push_back(levels.back() * i);
  }
  if (k >= levels.back()) k %= levels.back();
  sort(nums.begin(), nums.end());
  for (int i = 0; i < n - 1 && k > 0; ++i) {
    int level = k / levels[n - 1 - i];
    int tmp = nums[i + level];
    for (int j = i + level; j > i; --j) nums[j] = nums[j - 1];
    nums[i] = tmp;
    k -= (level * levels[n - 1 - i]);
  }
}






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值