剑指offer面试题38:字符串的排列

题目描述:输入一个字符串,打印出该字符串中字符的所有排列。

你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。

示例: 输入:s = "abc" 输出:["abc","acb","bac","bca","cab","cba"]

1、使用C++ next_permutation()全排列函数 

class Solution {
public:
    vector permutation(string s) 
    {
        sort(s.begin(),s.end());
        vector ans;
       //next_permutation是一个全排列函数,可以输出下一个值更大的排列
       //next_permutation()会取得[first,last)所标示之序列的下一个排列组合,
       //如果没有下一个排列组合,便返回false; 否则返回true。
       do
       {
           ans.push_back(s);
       }while(next_permutation(s.begin(),s.end()));
       return ans;
    }
};

2、 递归回溯

一般来说:组合问题和排列问题是在树形结构的叶子节点上收集结果,而子集问题就是取树上所有节点的结果

去重最为关键的代码为:

if (i > 0 && s[i] == s[i - 1] && visited[i - 1] == false) { continue; }

如果改成 used[i - 1] == true, 也是正确的!,去重代码如下:

if (i > 0 && s[i] == s[i - 1] && visited[i - 1] == true) { continue; }

如果要对树层中前一位去重,就用visited[i - 1] == false,如果要对树枝前一位去重用visited[i - 1] == true。

对于排列问题,树层上去重和树枝上去重,都是可以的,但是树层上去重效率更高!

class Solution {
public:
    vector res;
    string path;
    vector permutation(string s) 
    {
        res.clear();
        path.clear();
        sort(s.begin(),s.end()); //排序用来去重
        vector visited(s.size(),false);
        backtrack(s,visited);
        return res;
    }
    void backtrack(string s,vector& visited)
    {
        //递归终止条件:字符串s的所有字符都遍历了一遍
        if(s.size() == path.size()) 
        {
            res.push_back(path); //找到一条符合题意的路径
            return;
        }

        for(int i=0;i0 && s[i] == s[i-1] && visited[i-1] == false)
            if(i>0 && s[i] == s[i-1] && visited[i-1] == true) //字符串存在字符相同的情况
            {
                continue;
            }
            if(visited[i] == false) //当前字符没有遍历
            {
                path.push_back(s[i]);  //将未遍历的字符插入 path 字符数组中
                visited[i] = true;  //标注该字符已访问
                backtrack(s,visited); //递归访问下一个未访问的字符
                path.pop_back(); //回溯,删除最后一位字符
                visited[i] = false; 
            }
        }
    }
};
class Solution {
public:
    vector res;  //返回结果
    vector visited;          //标记数组,用来记录元素是否被访问过
    vector permutation(string s) 
    {
        sort(s.begin(),s.end());
        string temp = "";
        visited.resize(s.size(),false);
        backtrack(s,temp);
        return res;
    }
    void backtrack(string s,string& temp)
    {
        //递归终止条件
        if(temp.size() == s.size())
        {
            res.push_back(temp);
            return;
        }
        for(int i = 0; i < s.size(); i++)
        {
            //避免在同一位置填入重复元素
            if(visited[i] || (i>0 && s[i] == s[i-1] && visited[i-1] == false))
            {
                continue;
            }
            temp.push_back(s[i]);
            visited[i] = true;
            backtrack(s,temp);
            temp.pop_back();
            visited[i] = false;
        }
    }
};

 

总结:子集、组合类问题,关键是用一个start参数来控制选择列表!!若有重复元素,应该先排序后去重!最后回溯六步走:
①画出递归树,找到状态变量(回溯函数的参数),这一步非常重要※
②根据题意,确立结束条件
③找准选择列表(与函数参数相关),与第一步紧密关联※
④判断是否需要剪枝
⑤作出选择,递归调用,进入下一层
⑥撤销选择

总结:可以发现“排列”类型问题和“子集、组合”问题不同在于:“排列”问题使用used数组来标识选择列表,而“子集、组合”问题则使用start参数

你可能感兴趣的:(剑道offer,leetcode)