剑指offer27_字符串的排列_题解

字符串的排列

题目描述

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则按字典序打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

输入描述

输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。

示例1

输入

"ab"

返回值

["ab","ba"]

分析

方案一:回溯搜索+剪枝

全排列的基础上增加了序列中的元素可重复这一条件,但要求:返回的结果又不能有重复元素。

思路是:在遍历的过程中,一边遍历一遍检测,在一定会产生重复结果集的地方剪枝

对比图中标注 ① 和 ② 的地方。相同点是:这一次搜索的起点和上一次搜索的起点一样。不同点是:

  • 标注 ① 的地方上一次搜索的相同的数刚刚被撤销;
  • 标注 ② 的地方上一次搜索的相同的数刚刚被使用。

image.png

代码

/**
时间复杂度:O(N×N!)
这里 N 为数组的长度。
空间复杂度:O(N×N!)
**/
class Solution
{
public:
    void dfs(string s, int len, int depth, vector<bool> &used, string path, vector<string> &res)
    {
        if (depth == len)
        {
            res.emplace_back(path);
            return;
        }
        for (int i = 0; i < len; i++)
        {
            if (used[i])
            {
                continue;
            }
            //剪枝条件:i>0是为了保证s[i-1]有意义
            // 写 !used[i - 1] 是因为 nums[i - 1] 在深度优先遍历的过程中刚刚被撤销选择
            if (i > 0 && s[i] == s[i - 1] && !used[i - 1])
            {
                continue;
            }
            path.push_back(s[i]);
            used[i] = true;
            dfs(s, len, depth + 1, used, path, res);
            used[i] = false;
            path.pop_back();
        }
    }
    vector<string> Permutation(string str)
    {
        vector<string> res;
        int len = str.length();
        // 特判
        if (len == 0)
        {
            return res;
        }
        vector<bool> used(len, false);//访问数组
        string path = "";
        // 排序
        sort(str.begin(), str.end());
        // 回溯
        dfs(str, len, 0, used, path, res);
        return res;
    }
};

方案二:递归交换+剪枝+set去重

根据字符串排列的特点,考虑深度优先搜索所有排列方案。即通过字符交换,先固定第 1 位字符( n 种情况)、再固定第 2 位字符( n-1 种情况)、... 、最后固定第 n 位字符( 1 种情况)。

Picture1.png

重复方案与剪枝: 当字符串存在重复字符时,排列方案中也存在重复方案。为排除重复方案,需在固定某位字符时,保证 “每种字符只在此位固定一次” ,即遇到重复字符时不交换,直接跳过。从 DFS 角度看,此操作称为 “剪枝” 。

Picture2.png

代码

/**
时间复杂度:O(N!): N为字符串s的长度,方案数为N*(N-1)*(N-2)...*2*1=N!
空间复杂度:O(N^2):全排利的递归深度为N,系统累计使用栈空间大小为O(N);递归中辅助set累计存储的字符数量最多为N+(N-1)+...+2+1=(N+1)N/2,即占用O(N^2)的额外空间
**/
class Solution
{
public:
    vector<string> ans;
    vector<char> c;
    void dfs(int x)
    {
        if (x == c.size() - 1)
        {
            string res(c.begin(), c.end()); // 添加排列方案
            ans.push_back(res);
            return;
        }
        set<char> st;
        for (int i = x; i < c.size(); i++)
        {
            if (st.count(c[i]))
                continue;
            st.insert(c[i]);
            swap(c[i], c[x]); // 交换,将 c[i] 固定在第 x 位
            dfs(x + 1);       // 开启固定第 x + 1 位字符
            swap(c[i], c[x]); // 恢复交换
        }
    }
    vector<string> Permutation(string str)
    {
        for (int i = 0; i < str.size(); i++)
        {
            c.push_back(str[i]);
        }
        dfs(0);
        sort(ans.begin(), ans.end());
        return ans;
    }
};

方案三:递归交换+set自动去重

将所有排列方案插入set集合进行自动去重

代码

class Solution
{
public:
    void dfs(string s, int start, set<string> &visited)
    {
        if (start == s.size())
        {
            visited.insert(s);
            return;
        }
        for (int i = start; i < s.size(); i++)
        {
            //做选择
            swap(s[i], s[start]);
            dfs(s, start + 1, visited);
            //交换回去
            swap(s[i], s[start]);
        }
    }
    vector<string> Permutation(string str)
    {
        set<string> visited;
        dfs(str, 0, visited);
        return vector<string>(visited.begin(), visited.end());
    }
};
posted @ 2020-12-28 16:36  RiverCold  阅读(56)  评论(0编辑  收藏  举报