全排列问题
- 问题:如何获得给定集合的全排列。下面介绍两种解决方式。
1. 无重复集合的全排列
-
问题:求"qwe"的全排列。
- 显然,问题的答案是:“qwe”,“qew”,“eqw”,“ewq”,“wqe”,“weq”,共A(3,3)=6种。
-
一种朴素的解决方式如下:
- 对于串S,如果处理其区间
[l,r]
之间的字符的全排列,可以先固定S[l]
,然后求串S区间[l+1,r]
之间字符的全排列。S[l]
可以等于区间[l,r]
之间的所有字符。
- 对于串S,如果处理其区间
-
根据上述想法,可以写出如下代码:
#include<bits/stdc++.h> using namespace std; int coun = 0; void perm(string& str, int l, int r){ if (l == r){ for (int i = 0; i <= r; i ++){ cout << str[i] << " "; } cout << endl; coun ++; }else { for (int i = l; i <= r; i++){ swap(str[i], str[l]); perm(str, l+1, r); swap(str[i], str[l]); } } } int main(){ string str = "qwe"; perm(str, 0, str.size()-1); cout << coun << endl; }
-
关于上述算法的几个叙述:
- 时间复杂度:如果忽略打印的时间复杂度,则时间复杂度是O(A(n,n));n是串S的长度。
- 时间复杂度存疑,欢迎指教和改正。
- 显然,上述算法只能处理不重复字符串的全排列问题(因为它将所有的字符视为不同)。
- 时间复杂度:如果忽略打印的时间复杂度,则时间复杂度是O(A(n,n));n是串S的长度。
2.解决有重复集合的全排列
-
引入字典序解决无重复集合的全排列。
- reference:https://blog.csdn.net/kuailexiaoziwqx/article/details/52206384
- 问题:
839647521
是数字1~9的一个排列。如何获得它的下一个全排列呢? - 从它生成下一个排列的步骤如下:
- 自右至左找出排列中第一个比右边数字小的数字
4
=> 839647521 - 在该数字后的数字中找出比
4
大的数中最小的一个5
=> 839647521 - 交换4和5 => 839657421
- 将7421倒转 => 839651247
- 自右至左找出排列中第一个比右边数字小的数字
- 最终得到
839647521
的下一个排列:839651247
-
根据上述思想,可以写出如下代码:
#include<bits/stdc++.h> using namespace std; int main(){ string str = "1234"; int len = str.size(); cout << str << endl; int coun = 1; while (true){ int j = len - 2; while (j >= 0 && str[j] > str[j+1]){ j --; } if (j < 0){ break; } int k = len - 1; while (str[k] < str[j]){ k --; } swap(str[j], str[k]); reverse(str.begin()+j+1, str.end()); cout << str << endl; coun ++; } cout << coun << endl; return 0; }
-
考虑在上述过程中加入重复的字符。
- 如果
swap(str[j],str[k])
时,str[j]
和str[k]
相等,我们对这二者进行交换便没有意义了。- 因此,修改
while(str[k] < str[j])
为while(str[k] <= str[j])
,这样就不会存在str[j]
和str[k]
相等的情况了。
- 因此,修改
- 考虑
3221
的下一个排列,显然这个数字没有下一个排列。但是根据while(j >= 0 && str[j] > str[j+1])
,条件中的后者不成立,因此j并不会变成负数,因此会陷入死循环。于是将代码改为while(j >= 0 && str[j] >= str[j+1])
。
- 如果
-
修改完代码以后:
#include<bits/stdc++.h> using namespace std; int main(){ string str = "1123"; int len = str.size(); cout << str << endl; int coun = 1; while (true){ int j = len - 2; while (j >= 0 && str[j] >= str[j+1]){ j --; } if (j < 0){ break; } int k = len - 1; while (str[k] <= str[j]){ k --; } swap(str[j], str[k]); reverse(str.begin()+j+1, str.end()); cout << str << endl; coun ++; } cout << coun << endl; return 0; }
- 这样就解决了有重复集合的全排列问题了。
-
关于上述代码的几个讨论
- 时间复杂度:O(n*A(n,n));
- 存疑
- 时间复杂度:O(n*A(n,n));
力扣中的问题解决
-
面试题 08.08.有重复字符串的排列组合
class Solution { public: vector<string> permutation(string S) { vector<string> ret; string str = S; sort(str.begin(), str.end()); ret.push_back(str); int len = str.size(); while (true){ int j = len - 2; while (j >= 0 && str[j] >= str[j+1]){ j --; } if (j < 0){ break; } int k = len - 1; while (str[j] >= str[k]){ k --; } swap(str[k], str[j]); reverse(str.begin()+j+1, str.end()); ret.push_back(str); } return ret; } };
写在后面
- 今天是2020.9.11,今天我来到了宁波,到了浙大软件学院报到。其实是昨天到的,住了宾馆。有点不悦地离开了家,现在有点累,现在突然有点想家,真是让人赧然啊。不写了,明日还得体检。