0
点赞
收藏
分享

微信扫一扫

[典型]BM55 没有重复项数字的全排列-中等

排列,一般地,从n个不同元素中取出m(m≤n)个元素,按照一定的顺序排成一列,叫做从n个元素中取出m个元素的一个排列(permutation)。特别地,当m=n时,这个排列被称作全排列(all permutation)。

对于N个不同元素进行全排列,可以得到N!种不同的序列,其中:

n! = n*(n-1)*(n-2)*...*2*1

如果要从N个不同元素中取出M个元素进行排列,则一共有:

[典型]BM55 没有重复项数字的全排列-中等_记忆化 = n*(n-1)*(n-2)*...*(n-m+1)

​​牛客网--BM55 没有重复项数字的全排列​​

描述

给出一组没有重复元素的数字,返回该组数字的所有排列例如:[1,2,3]的所有排列如下

[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2], [3,2,1].

(以数字在数组中的位置靠前为优先级,按字典序排列输出。)

数据范围:数字个数 [典型]BM55 没有重复项数字的全排列-中等_回溯_02要求:空间复杂度 [典型]BM55 没有重复项数字的全排列-中等_递归_03 ,时间复杂度 [典型]BM55 没有重复项数字的全排列-中等_记忆化_04

示例1

输入:

[1,2,3]

复制返回值:

[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

复制

示例2

输入:

[1]

复制返回值:

[[1]]
暴力递归求解

对于一个长度为N的没有重复它的全排列可以通过从这N个元素中的任意一个元素与剩下N-1个元素的全排列的组合。可以使用递归进行求解,步骤如下:

  1. 如果数组长度为0,则返回空,为1直接返回数组
  2. 遍历整个数组,将当前遍历的索引为i的元素从数组中取出,对剩余的N-1个元素递归求解

代码如下:

std::vector<std::vector<int>> perm_imp(std::vector<int> num)
{
std::vector<std::vector<int>> res;
if (num.size() == 0)
{
return res;
}
if (num.size() == 1)
{
res.push_back(num);
return res;
}

for (int i = 0; i < num.size(); ++i)
{
std::vector<int> cur = num;
cur.erase(cur.begin() + i);
auto cur_perm = perm_imp(cur);
for (auto &x : cur_perm)
{
x.push_back(num[i]);
res.push_back(x);
}
}
return res;
}

std::vector<std::vector<int>> permute(std::vector<int> &num)
{
return perm_imp(num);
}
回溯+交换的实现

在暴力递归求解中,我们需要将每个长度为1~N的数组都作为值传递,并且还需要为下次的递归准备一个长度减一的数组,需要申请大量的空间。有没有方法避免这些空间的消耗呢?答案是可以的。

实际上对于N个没有重复元素的数组的全排列中的任意一种,我们都是可以通过有限次的元素位置的交换得到。而交换位置的空间复杂度是O(1)。那么该如何交换元素才能得到所有的排列,并且不回出现重读的排列呢?

假设我们从第一个位置开始,它不与其他元素交换的时候本身就是一个排列。当他与剩余的每一个元素进行交换的时候,就得到了剩下的N-1种排列。这N种排列又分别有(N-1)!种排列,又可以使用递归来求解。

步骤如下:

  1. 遍历整个数组,选取[0,n-1]位置的元素与第一个元素进行交换,然后再对剩下的N-1个元素递归求解。当某个索引位置的子问题求解完成后,需要将数组还原成原始数组,以保数组的顺序不会出现乱序的情况
  2. 当子问题的索引位置为n-1的时候,表示整个数组交换完毕,将当前数组放入结果中

代码如下:

void perm_imp(std::vector<std::vector<int>> &res, std::vector<int> &num, int index)
{
if (index == num.size() - 1)//分枝进入结尾,找到一种排列
{
res.push_back(num);
return;
}

//遍历后续的元素
for (int i = index; i < num.size() - 1; ++i)
{
std::swap(num[i], num[index]);
perm_imp(res, num, index + 1);
std::swap(num[i], num[index]);//回溯
}
}

std::vector<std::vector<int>> permute(std::vector<int> &num)
{
std::sort(num.begin(), num.end());//先按字典序排序
std::vector<std::vector<int>> res;
perm_imp(res, num, 0);
return res;
}


回溯+记忆化的实现

由于数组的长度为N,其全排列可以看做是从这N个元素中每次在任意一个位置选取一个元素,共选取N次后拼接得到的。那么要如何选取才能保证已经被选取过的元素在本次的排列中不会重复呢?答案是使用一个状态数组保存选取的状态,在进入选取前设置为已经选取,在子递归完成后恢复成未选取的状态。如果已经选取,则在本次选取的时候需要跳过。在每次选取后将结果放入一个临时数组中,当临时数组长度为N的时候将其加入结果中。

代码如下:

vector<vector<int>> permute(vector<int> &num)
{
vector<vector<int>> res;
dfs(res, vector<int>(), vector<int>(num.size(), 0), num);
return res;
}

void dfs(vector<vector<int>> &res, vector<int> tmp, vector<int> visited, vector<int> &num)
{
if (tmp.size() == num.size())
{
res.push_back(tmp);
return;
}
for (int i = 0; i < num.size(); ++i)
{
if (visited[i] == 1)
continue;
visited[i] = 1;
tmp.push_back(num[i]);
dfs(res, tmp, visited, num);
tmp.pop_back();
visited[i] = 0;
}
}

举报

相关推荐

0 条评论