常见的题,给一个数组返回所有排列。
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
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]);
}
}