【算法】[leetcode] permutations的讨论(转载)

原题是找到一组数的全排列

Given a collection of numbers, return all possible permutations.

For example,
[1,2,3] have the following permutations:
[1,2,3][1,3,2][2,1,3][2,3,1][3,1,2], and [3,2,1].

函数原型:

vector<vector<int> > permute(vector<int> &num;

这个题大眼一看就是思路一大坨,这里做一个整理吧

思路1

比较直观的想法就是递归咯~~ 在num中拿出1个数字放在第一个,然后剩下的数字做一个全排列,最早接触这个问题的时候我就是这么写的

[cpp] view plaincopy

  1. class Solution {
  2. public:
  3. vector<vector<int> > permute(vector<int> &num) {
  4. // Start typing your C/C++ solution below
  5. // DO NOT write int main() function
  6. int N = num.size();
  7. vector<vector<int> > ret;
  8. if(N == 1){
  9. ret.push_back(num);
  10. return ret;
  11. }
  12. vector<vector<int> > post;
  13. vector<int> cur;
  14. vector<int> tmp;
  15. for(int i = 0; i < N; i++){
  16. cur = num;
  17. cur.erase(cur.begin()+i);
  18. post = permute(cur);
  19. for(int j = 0; j < post.size(); j++){
  20. tmp = post[j];
  21. tmp.insert(tmp.begin(), num[i]);
  22. ret.push_back(tmp);
  23. }
  24. }
  25. return ret;
  26. }
  27. };

思路2:

建立一棵树,比如说

对于第k层节点来说,就是交换固定了前面 k-1 位,然后分别 swap(k,k), swap(k, k+1) , swap(k, k+2) ...

例如上图中的第三层,固定了第一位(即2),然后分别交换第1,1位,1,2位,1,3位

[cpp] view plaincopy

  1. class Solution {
  2. vector<vector<int> > ret;
  3. int N;
  4. public:
  5. void perm(vector<int> &num, int i){
  6. if( i == N){
  7. ret.push_back(num);
  8. }
  9. for(int j = i; j < N; j++){
  10. swap(num[i], num[j]);
  11. perm(num, i + 1);
  12. swap(num[j], num[i]);
  13. }
  14. }
  15. vector<vector<int> > permute(vector<int> &num) {
  16. // Start typing your C/C++ solution below
  17. // DO NOT write int main() function
  18. N = num.size();
  19. ret.clear();
  20. perm(num, 0);
  21. return ret;
  22. }
  23. };

思路3

stl的algorithm里面其实是有next permutation的算法的,那其实用next permutation的方法也是一个不错的选择

这个思路可以保证遍历的顺序是字典序,即按照从小到大的顺序

next permutation的算法就是。。。swap + reverse。。。交换 & 倒叙

比如 1,2,3的下一个就是1,3,2这个很容易理解,因为2和3是升序的,只需要交换这两位,那么132 > 123,但是如果后面几位都是倒序的怎么办?

例如 5,4,7,5,3,2 这个序列

我们知道答案应该是 5,5,2,3,4,7

从直观上来说,7,5,3,2已经是这四位排列的最大值了,所以一定要动到 4 这个数字了,所以我们选了刚好比4大的5来和4进行交换,得到 5,5,。。。后面几位就按照升序放进去就可以了

但是令人兴奋的一点是,当4和5交换以后,后面的序列一定是倒序的,所以我们不需要重新sort,只需要将其reverse就可以了

这就是swap  + reverse的思路

注意,下面这个代码里面交换的是 i-1 和 j-1 所以i指向的是7,而j指向的是3

[cpp] view plaincopy

  1. class Solution {
  2. public:
  3. void nextPermutation(vector<int> &num) {
  4. // Start typing your C/C++ solution below
  5. // DO NOT write int main() function
  6. //5,4,7,5,3,2
  7. //    |   |
  8. //    i   j
  9. //5,5,7,4,3,2
  10. //5,5,2,3,4,7
  11. int i = num.size()-1;
  12. while(i > 0 && num[i-1] >= num[i] ){
  13. i--;
  14. }
  15. int j = i;
  16. while(j < num.size() && num[j] > num[i-1]) j++;
  17. if(i == 0){
  18. reverse(num.begin(), num.end());
  19. }else{
  20. swap(num[i-1], num[j-1]);
  21. reverse(num.begin() + i, num.end());
  22. }
  23. }
  24. int factorial(int n){
  25. return (n == 1 || n == 0) ? 1 : factorial(n - 1) * n;
  26. }
  27. vector<vector<int> > permute(vector<int> &num) {
  28. // Start typing your C/C++ solution below
  29. // DO NOT write int main() function
  30. int N = num.size();
  31. vector<vector<int> > ret;
  32. ret.push_back(num);
  33. for(int i = 1; i < factorial(N); i++){
  34. nextPermutation(num);
  35. ret.push_back(num);
  36. }
  37. return ret;
  38. }
  39. };

思路四

我觉得思路4是一个很常规的思路,很多把recursive的code改成iterative的code都会用到这样的方法,其实呢,它的本质就是把N个for改成while的方法。介个方法在编程之美里面的“电话号码”那一节提到过,不明白的童鞋可以去看一看,我觉得第一次想写粗来还是很难的,不过多写几个,就会很熟练啦

对应介个题目的思路捏就是。。。举个例子来说吧

如果我想求1,2,3,4的全排列

偶的思路就是建一个特殊的数,它的进位方法是 3, 2, 1, 0

所以,这个数的++过程就是

0000 -> 0010 -> 0100 -> 0110 ->0200 -> 0210 ->

1000 -> 1010 -> 1100 -> 1110 ->1200 -> 1210 ->

2000 -> 2010 -> 2100 -> 2110 ->2200 -> 2210 ->

3000 -> 3010 -> 3100 -> 3110 ->3200 -> 3210

哇哈哈哈,刚好是24个!

然后捏? b0 b1 b2 b3就代表在当前剩下的数字中选择第bi个

哇!好复杂。。。

比如0210

0: 在1234中选择第0个,就是1

2: 在234中选择滴2个,就是4

1: 在23中选择第1个,就是3

0: 在2中选择第0个,就是2

所以0210对应点就素 1432

[cpp] view plaincopy

  1. class Solution {
  2. public:
  3. int factorial(int n){
  4. return (n == 1 || n == 0) ? 1 : factorial(n - 1) * n;
  5. }
  6. void plusp(vector<int> &p, const vector<int> &bound){
  7. int i = p.size()-1;
  8. while(i >= 0){
  9. if(p[i] < bound[i]){
  10. p[i]++;
  11. break;
  12. }else{
  13. p[i] = 0;
  14. i--;
  15. }
  16. }
  17. }
  18. vector<vector<int> > permute(vector<int> &num) {
  19. // Start typing your C/C++ solution below
  20. // DO NOT write int main() function
  21. vector<vector<int> > ret;
  22. vector<int> ori_num = num;
  23. vector<int> tmp = num;
  24. int N = num.size();
  25. vector<int> p(N, 0);
  26. vector<int> bound = num;
  27. for(int i = 0; i < N; i++){
  28. bound[i] = N - 1 - i;
  29. }
  30. for(int i = 0; i < factorial(N); i++){
  31. num = ori_num;
  32. for(int j = 0; j < N; j++){
  33. tmp[j] = num[p[j]];
  34. num.erase(num.begin() + p[j]);
  35. }
  36. ret.push_back(tmp);
  37. plusp(p, bound);
  38. }
  39. return ret;
  40. }
  41. };

关于字典序的补充版本

Given a collection of numbers that might contain duplicates, return all possible unique permutations.

For example,

[1,1,2]

have the following unique permutations:

[1,1,2] ,  [1,2,1] , and  [2,1,1] .

Permutations 的升级版,依旧是全排列问题,但是 序列中可能会出现重复数字 。

思路:采用字典序的非递归方法。 从字典顺序最小的一种排列开始,每次获得字典序刚好比前一个排列大的排列,直到得到字典序最大的排列时,就得到了所有的结果, 以字符串"abc"为例,"abc"是字典序最小的排列,所有情况按字典序排列为"abc","acb","bac","bca","cba","cab"。

具体步骤为为:

1.字符串进行排序,得到字符串的最小字典序排列(C0C1C2...Cn),Ci<=Ci+1。

2.从后往前,找到一对相邻的升序元素CiCi+1,(Ci<Ci+1),如果遍历完字符串找不到这样的相邻升序对,说明已经达到了字典序最大的全排列

3.从字符串结束位置到位置i遍历,找到比Ci大的元素Cj,交换Cj的位置

4.将Ci+1到Cn所有的字符逆序,这样得到的排列刚好比之前的字典序大(因为转换后Ci+1<Ci+2<...<Cn,为最小字典序)。

5.重复3,4,5过程直到字典序最大。

AC code:

class Solution {
public:
  void swap(int &i,int &j)
  {
    int temp=i;
    i=j;
    j=temp;
  }
  vector<vector<int> > permuteUnique(vector<int> &num)
  {
    vector<vector<int>> res;
    int i,j,n=num.size();
    sort(num.begin(),num.end());
    res.push_back(num);
    while(true)
    {
      for(i=n-2;i>=0;i--)
        if(num[i]<num[i+1])
          break;
      if(i<=-1)
        return res;
      for(j=n-1;j>i;j--)
        if(num[j]>num[i])
          break;
      swap(num[i],num[j]);
      for(int k=i+1;k<(i+1+n)/2;k++)
        swap(num[k],num[n-(k-i)]);
      res.push_back(num);
    }
  }
};
时间: 2024-11-06 19:01:55

【算法】[leetcode] permutations的讨论(转载)的相关文章

LeetCode: Permutations [045]

[题目] Given a collection of numbers, return all possible permutations. For example, [1,2,3] have the following permutations: [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], and [3,2,1]. [题意] 给定一个数组,生成所有的全排列 [思路] 递归,类DFS [代码] class Solution { public: void

LeetCode: Permutations II [046]

[题目] Given a collection of numbers that might contain duplicates, return all possible unique permutations. For example, [1,1,2] have the following unique permutations: [1,1,2], [1,2,1], and [2,1,1]. [题意] 给定一个候选数集合,候选集中可能存在重复数,返回所有的排列 [思路] 思路和Permutat

Leetcode:Permutations 排列

戳我去解题 Given a collection of numbers, return all possible permutations. For example,[1,2,3] have the following permutations:[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], and [3,2,1]. 解题分析:首先进行排序,保证结果保持字典序 class Solution { public: vector<vector<int>

算法 - leetcode 292 Nim Game

算法 - leetcode 292 Nim Game  一丶题目 你和你的朋友,两个人一起玩 Nim 游戏:桌子上有一堆石头,每次你们轮流拿掉 1 - 3 块石头. 拿掉最后一块石头的人就是获胜者.你作为先手. 你们是聪明人,每一步都是最优解. 编写一个函数,来判断你是否可以在给定石头数量的情况下赢得游戏. 输入: 4 输出: false 解释: 如果堆中有 4 块石头,那么你永远不会赢得比赛: 因为无论你拿走 1 块.2 块 还是 3 块石头,最后一块石头总是会被你的朋友拿走. 二丶思路 1)

前端与算法 leetcode 28.实现 strStr()

# 前端与算法 leetcode 28.实现 strStr() 题目描述 28.移除元素 概要 这道题的意义是实现一个api,不是调api,尽管很多时候api的速度比我们写的快(今天这个我们可以做到和indexOf一样快),但我们还是要去了解api内实现的原理,在我们所熟悉的v8引擎中,indexOf使用了kmp和bm两种算法,在主串长度小于7时使用kmp,大于7的时候使用bm,bf咱就不说了哈,那个其实就是爆破算法, 提示 数据结构,kmp,bm 解析 kmp算法的核心其实就是动态规划,明确了

前端与算法 leetcode 189. 旋转数组

目录 # 前端与算法 leetcode 189. 旋转数组 题目描述 概要 提示 解析 算法 # 前端与算法 leetcode 189. 旋转数组 题目描述 189. 旋转数组 概要 把他当做一到简单的题来做,不要想太多了就好也可以不整那些花里胡哨的,直接旋转数组n次,我一开始也想到了这个办法,但是觉得太简单而且效率低下,想了很久也没想到合适的办法 提示 使用额外的数组 解析 用一个额外的数组将每个元素放到对应的位置就好 下标为i的位置对应(i+k)%数组长度 ,然后把新的数组拷贝(深拷贝)到原

前端与算法 leetcode 26. 删除排序数组中的重复项

目录 # 前端与算法 leetcode 26. 删除排序数组中的重复项 题目描述 概要 提示 解析 算法 # 前端与算法 leetcode 26. 删除排序数组中的重复项 题目描述 26. 删除排序数组中的重复项 概要 一提到原地删除数组,就能立即想到双指针法,这道题本身也没什么难度,日常水题, 提示 双指针 解析 没有思路的时候,耐心一点即可 算法 /** ?*[email protected]?{number[]}?nums ?*[email protected]?{number} ?*/

前端与算法 leetcode 350. 两个数组的交集 II

目录 # 前端与算法 leetcode 350. 两个数组的交集 II 题目描述 概要 提示 解析 解法一:哈希表 解法二:双指针 解法三:暴力法 算法 # 前端与算法 leetcode 350. 两个数组的交集 II 题目描述 给定两个数组,编写一个函数来计算它们的交集. 示例 1: 输入: nums1 = [1,2,2,1], nums2 = [2,2] 输出: [2,2] 示例 2: 输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4] 输出: [4,9] 说明

前端与算法 leetcode 1. 两数之和

目录 # 前端与算法 leetcode 1. 两数之和 题目描述 概要 提示 解析 解法一:暴力法 解法二:HashMap法 算法 传入[2,7,11,1,12,34,4,15],19的运行结果 执行结果 GitHub仓库 # 前端与算法 leetcode 1. 两数之和 题目描述 给定一个整数数组 nums?和一个目标值 target,请你在该数组中找出和为目标值的那?两个?整数,并返回他们的数组下标. 你可以假设每种输入只会对应一个答案.但是,你不能重复利用这个数组中同样的元素. 示例: 给