【剑指offer-C++】JZ38:字符串的排列

【剑指offer-C++】JZ38:字符串的排列

题目描述

描述:输入一个长度为 n 字符串,打印出该字符串中字符的所有排列,你可以以任意顺序返回这个字符串数组。例如输入字符串ABC,则输出由字符A,B,C所能排列出来的所有字符串ABC,ACB,BAC,BCA,CBA和CAB。

在这里插入图片描述

数据范围:n<10。

要求:空间复杂度 O(n!),时间复杂度 O(n!)。

输入描述:输入一个字符串,长度不超过10,字符只包括大小写字母。

解题思路

字符串的排列:最直观的想法是,回溯中的全排列,只不过该全排列中的元素可能重复,故需要考虑去重。使用res表示所有结果,使用path表示当前结果,使用used表示元素是否遍历,使用backtracking表示回溯。如果path的长度等于str的长度,那么表明已经到达叶子节点,故需要将path加入到res中并返回;直接依次遍历str中的每一个元素,使用used判断当前元素是否使用过,如果使用过则继续下一轮continue,反之标记当前元素已经被使用并将其加入到当前路径中,同时继续回溯,回溯完后再弹出元素并置为未访问。由于全排列中的元素可能重复,但是结果数组要求不重复,故使用unordered_set去重,最后使用迭代器将其转换为vector<string>即可。注意,回溯类型的题目,无论是排列还是组合,都可以通过画树形图来模拟解决。以字符串"ABC"为例,先A再B再C,到C时回溯返回弹出C,然后backtracking结束回到B再弹出B,当时i=1,故继续i=2,此时C加入,然后再回溯到B,就完成ABC和ACB,其余的以此类推。

unordered_set<string> res; //结果数组
string path; //当前结果
void backtracking(vector<bool> &used,string &str)
{
   //当前结果数组等于字符串长度则收集结果
   if(path.size()==str.size())
   {
       res.emplace(path);
       return;
   }
   //遍历字符串元素
   for(int i=0;i<str.size();i++)
   {
      //如果已经使用过则下一轮
      if(used[i]==true)
         continue;
      //标记使用
      used[i]=true;
      //加入当前路径
      path.push_back(str[i]);
      //继续回溯
      backtracking(used, str);
      //回溯结束后置为未访问以留下一次
      used[i]=false;
      //弹出元素
      path.pop_back();
    }
}
vector<string> Permutation(string str) 
{
    if(str.size()==0) //空字符串
       return vector<string>({""});
    //使用数组
    vector<bool> used(str.size(),false);
    backtracking(used, str);
    return vector<string>(res.begin(),res.end());
}

优化:上述方法中,使用了额外的存储空间,那么能否不使用呢?当然可以!我们可以发现,上述的字符串重复,主要是由于相同元素引起的,那么我们可以通过先对字符串进行排序,然后再结合used数组和str字符串来进行去重,即树枝上的元素可以重复,但是树层上的元素不可以重复,因为如果树层上重复,那么就出现了同前面相同的排列。

vector<string> res; //结果数组
string path; //当前结果
void backtracking(vector<bool> &used,string &str)
{
   //结果数组等于字符串长度则收集结果
   if(path.size()==str.size())
   {
      res.push_back(path);
      return;
   }
   //遍历字符串元素
   for(int i=0;i<str.size();i++)
   {
      //树枝上可以重复树层上不可以重复 used[i-1]=false表示树层上重复 因为是回溯后的结果
      if(str[i]==str[i-1]&&i>=1&&used[i-1]==false)
         continue;
      //如果已经使用过则下一轮
      if(used[i]==true)
         continue;
      //标记使用
      used[i]=true;
      //加入当前路径
      path.push_back(str[i]);
      //继续回溯
      backtracking(used, str);
      //回溯结束后置为未访问以留下一次
      used[i]=false;
      //弹出元素
      path.pop_back();
    }
}
vector<string> Permutation(string str) 
{
   if(str.size()==0) //空字符串
      return vector<string>({""});
   //使用数组
   vector<bool> used(str.size(),false);
   sort(str.begin(),str.end()); //该方法一定要先排序
   backtracking(used, str);
   return res;
}

PS:牛客可能会给你报错,但是你自己对结果和输出即可!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值