1079. 活字印刷
题意
- 字模无序。
- 每个字模仅能使用 1 次。
- 返回所有的非空子集的数量。
解法1 dfs + set
因为 tiles
的长度很小,所以可以遍历其长度,从而将题目转换成若干个 全排列 问题。
对于得到的重复的排列,使用 set
自动去重。
同层剪枝:
因为字模可能有重复,所以在 dfs 时会出现大量的重复序列,而这些重复序列的出现本质上是在 s[i] 位置上放了重复的字符。因此,
- 首先对
tiles
进行排序,使得重复的字符排列在一块儿 - 然后在
dfs
过程中,判断if(vis[i]||(i>0&&new_tiles[i]==new_tiles[i-1]&&!vis[i-1])) continue;
。对于重复的字符 a1a2a3,当 a1 被放在 s[i] 后,该层的 dfs 会回溯到上一步,此时 vis(a1) 被重置为 0 ,因此,当遍历到 a2 时,满足i>0&&new_tiles[i]==new_tiles[i-1]&&!vis[i-1]
,因此直接跳过。
class Solution {
public:
unordered_set<string> st;
void dfs(string tiles, string s, vector<int> vis, int len)
{
if(s.size()==len)
{
st.insert(s);
return;
}
for(int i=0;i<tiles.size();i++)
{
if(!vis[i])
{
s=s+tiles[i];
vis[i]=1;
dfs(tiles, s, vis, len);
s=s.substr(0,s.size()-1);
vis[i]=0;
}
}
}
int numTilePossibilities(string tiles) {
int n=tiles.size();
for(int i=1;i<=n;i++)
{
string s="";
vector<int> vis(n,0);
dfs(tiles, s, vis, i);
}
return st.size();
}
};
// 同层剪枝
class Solution {
public:
unordered_set<string> st;
void dfs(vector<char> new_tiles, string s, vector<int> vis, int len)
{
if(s.size()==len)
{
st.insert(s);
return;
}
for(int i=0;i<new_tiles.size();i++)
{
if(vis[i]||(i>0&&new_tiles[i]==new_tiles[i-1]&&!vis[i-1])) continue;
s=s+new_tiles[i];
vis[i]=1;
dfs(new_tiles, s, vis, len);
s=s.substr(0,s.size()-1);
vis[i]=0;
}
}
int numTilePossibilities(string tiles) {
int n=tiles.size();
vector<char> new_tiles;
for(int i=0;i<tiles.size();i++)
new_tiles.push_back(tiles[i]);
sort(new_tiles.begin(),new_tiles.end());
for(int i=1;i<=n;i++)
{
string s="";
vector<int> vis(n,0);
dfs(new_tiles, s, vis, i);
}
return st.size();
}
};
46. 全排列
题意
- 板子题
解法 dfs
全排列的实质是创建了一棵树,每一层对应了全排列的每个位置,对于位置 idx,首先插入一个未插入过的元素,然后 dfs 到底,然后回溯到位置 idx,再次插入一个未插入过的元素,所以,记得回溯时的更新!!!
class Solution {
public:
vector<vector<int>> anss;
void dfs(vector<int> num, vector<int> ans, vector<int> vis)
{
if(ans.size()==num.size()) //一次全排列结束,保存该次全排列
{
anss.push_back(ans);
return ;
}
for(int i=0;i<num.size();i++) //遍历所有元素
{
if(vis[i]!=1)
{
// 插入一个新元素
vis[i]=1;
ans.push_back(num[i]);
dfs(num, ans,vis);
// 回溯插入的元素,从而在同一个位置插入其他元素(一定要回溯!)
ans.pop_back();
vis[i]=0;
}
}
}
vector<vector<int>> permute(vector<int>& nums) {
vector<int> ans; //记录每次排列
vector<int> vis(nums.size(),0); //记录每个元素是否被排列
dfs(nums,ans,vis);
return anss;
}
};
47. 全排列 II
题意
- 带重复元素的全排列
解法 dfs
因为元素可能有重复,所以在 dfs 时会出现大量的重复序列,而这些重复序列的出现本质上是在 s[i] 位置上放了重复的字符。因此,
- 首先对
nums
进行排序,使得重复的字符排列在一块儿 - 然后在
dfs
过程中,判断if(vis[i]||(i>0&&nums[i]==nums[i-1]&&!vis[i-1])) continue;
。对于重复的字符 a1a2a3,当 a1 被放在 s[i] 后,该层的 dfs 会回溯到上一步,此时 vis(a1) 被重置为 0 ,因此,当遍历到 a2 时,满足i>0&&nums[i]==nums[i-1]&&!vis[i-1]
,因此直接跳过。
class Solution {
public:
vector<vector<int>> anss;
void dfs(vector<int> nums, vector<int> ans, vector<int> vis)
{
if(ans.size()==nums.size())
{
anss.push_back(ans);
return ;
}
for(int i=0;i<nums.size();i++)
{
// 被用过, 之前的重复数字被用过,跳过
if(vis[i]||(i>0&&nums[i]==nums[i-1]&&!vis[i-1]))
{
continue;
}
ans.push_back(nums[i]);
vis[i]=1;
dfs(nums,ans,vis);
vis[i]=0;
ans.pop_back();
}
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
sort(nums.begin(),nums.end()); //从小大 排序
vector<int> ans;
vector<int> vis(nums.size(),0);
dfs(nums,ans,vis);
return anss;
}
};