1. 题目来源
链接:47. 全排列 II
必看,必刷,dfs 经典:
- [Mdfs] lc39. 组合总和(dfs+经典)
- [Mdfs] lc40. 组合总和 II(dfs+经典)
- [Mdfs] lc46. 全排列(dfs+经典)
- [Mdfs] lc47. 全排列 II(dfs搜索顺序+去重处理+知识理解+经典)
- [Mdfs] lc77. 组合(组合类型枚举+题目总结+经典)
- [Mdfs] lc78. 子集(二进制枚举+排列类型枚举+经典)
- [Mdfs] lc90. 子集 II(组合类型枚举+多重背包+去重经典)
2. 题目解析
前导题:
依旧两种暴搜方案:数字枚举所有位置、位置枚举所有数字。若是要求字典序输出,则只能拿后者来做了,就顺序枚举所有空位置,且从小的数开始往进放,即可保证字典序。
针对重复元素的处理,可以先将整个数组排序,重复元素则相邻,可以在暴搜过程中加这样一个判断:如果和当前元素相等的前面一个元素还没有用,那么当前元素就不能用。即保证了排列前后相同数的相对位置不发生改变。这样就能去掉重复情况。
时间复杂度:
O
(
n
∗
n
!
)
O(n*n!)
O(n∗n!) 。不会分析,可见官方题解…
空间复杂度:
O
(
n
)
O(n)
O(n)
每个位置可以放哪些数:
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
vector<bool> st;
vector<vector<int>> permuteUnique(vector<int>& nums) {
sort(nums.begin(), nums.end());
st = vector<bool>(nums.size());
dfs(nums, 0);
return ans;
}
void dfs(vector<int> &nums, int u) {
if (u == nums.size()) {
ans.push_back(path);
return ;
}
// 枚举当前位置 u 可以合法放哪些数
for (int i = 0; i < nums.size(); i ++ ) {
if (!st[i]) {
if (i && nums[i - 1] == nums[i] && !st[i - 1]) continue;
path.push_back(nums[i]);
st[i] = true;
dfs(nums, u + 1);
path.pop_back();
st[i] = false;
}
}
}
};
每个数可以放在哪些位置:
这个写法请查看:,因为在这这个写法不太实用,不容易理解。
每个数放哪些位置,这样位置就散乱了,就需要记住相同的数上一个放的位置在哪,我们是顺序枚举所有的数的,那么放的位置就必须要在上一个相同的数的后面放,才能保证相对位置不变。
不失为一种方法,好好理解下吧。
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
vector<bool> st;
void dfs(int u, vector<int>& nums, int start) {
if (u == nums.size()) {
res.push_back(path);
return ;
}
// 枚举当前数 u 所能放的所有合法位置
for (int i = start; i < nums.size(); i ++ ) {
if (!st[i]) {
path[i] = nums[u];
st[i] = true;
if (u + 1 < nums.size() && nums[u + 1] == nums[u]) dfs(u + 1, nums, 0);
else dfs(u + 1, nums, i + 1);
st[i] = false;
}
}
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
int n = nums.size();
sort(nums.begin(), nums.end());
path.resize(n, 0);
st.resize(n, false);
dfs(0, nums, 0);
return res;
}
};