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

题目

输入一个字符串,打印出该字符串中字符的所有排列。

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

示例

输入:s = “abc”
输出:[“abc”,“acb”,“bac”,“bca”,“cab”,“cba”]

限制

1 <= s 的长度 <= 8

分析

这本身是一个全排列的题,但是并没有要求按照顺序来,使用回溯法即可。这道题难点在于有重复的元素,所以需要去重。

方案一

回溯法:
先用sort给数组排序,使相同元素相邻,使用一个visited数组来记录每个节点的状态,使用一个vector来存放节点,节点未遍历且相邻节点不是重复字符时,将节点加入到vector中,依次递归。

代码

class Solution {
public:
    vector<string> permutation(string s) {
        vector<string> res;
        if(s.empty()) return res;
        sort(s.begin(), s.end());
        int len = s.size();
        vector<bool> visited(len, false);
        string temp;
        backtrack(res, s, temp, visited);
        return res;
    }
    void backtrack(vector<string> &res, string s, string &temp, vector<bool> &visited) {
        if(temp.size() == s.size()) {
            res.push_back(temp);
            return;
        }
        for(int i = 0; i < s.size(); i++) {
            if(visited[i]) continue;
            /*
            比如有两个相邻元素xx,visited[i-1] = 1, visited[i] = 0表明第一个x加进去temp中了,所以不用continue;
            如果visited[i-1] = 0, 表明已经完成两个x的操作,此时只剩下一个x了,无法构成排列,直接continue
            */
            if(i > 0 && !visited[i-1] && s[i] == s[i-1]) continue;	//剪枝
            
            visited[i] = true;
            temp.push_back(s[i]);
            backtrack(res, s, temp, visited);
            temp.pop_back();
            visited[i] = false;
        }
    }
};

这里需要注意和理解的就是剪枝那一步。
假如有两个相同元素xx相邻,这里我们用 x1 和 x2 代替这两个 x 。重复的情况一定就是本来是xx,我们产生 x1x2 和 x2x1 两种情况。为了避免这种情况,我们就需要剪枝,我们便利是从前往后的,那么我们会先遇到 x1,然后令 visited[x1] = 1,当遍历到 x2 的时候,此时 x1 是遍历过的,也就是生成了x1x2这种结果。那么我们就需要在后面避免 x2x1 这种情况,这种情况的出现条件就是 visited[x1] = 0,然后遇到 x2 ,此时就可以直接continue。

方案二

交换法
每次固定一个字符,然后将固定字符与其他字符进行交换,依次遍历每个字符,再进行回溯递归。
使用set进行去重

代码

class Solution {
public:
    vector<string> permutation(string s) {
        set<string> res;
        backtrack(s, 0, res);
        return vector<string>(res.begin(), res.end());
    }
    void backtrack(string s, int start, set<string> &res) {
        // 结束条件
        if(start == s.size() - 1) {
            res.insert(s);
            return;
        }
        for(int i = start; i < s.size(); i++) {
            swap(s[i], s[start]);
            backtrack(s, start+1, res);
            swap(s[i], s[start]);
        }
    }
};

方案三

与方案二方法一致,但不是使用set去重,而实定义一个函数进行去重

代码


class Solution {
public:
    vector<string> permutation(string s) {
        vector<string> res;
        backtrack(s, 0, res);
        return res;
    }
    void backtrack(string &s, int start, vector<string> &res){
        if(start == s.size() - 1){
            res.push_back(s);
            return;
        }
        for(int i = start; i < s.size(); i++){
            if(judge(s, start, i)) continue;  //从start开始,遍历不重复的字符
            swap(s[start], s[i]);
            backtrack(s, start+1, res);
            swap(s[start], s[i]);
        }
    }
    bool judge(string& s, int start, int end) {
        for (int i = start; i < end; ++i) {
            if (s[i] == s[end]) return true;
        }
        return false;
    }
};

方案二并没有忽略掉相同元素之间的交换,所以存在重复解,需要靠set去重,而方案三在每一次遍历的时候都只判断不重复的字符,也就是只交换不相同的元素,这样就不会存在重复解。
judge函数里start到end-1里均是不同的元素,每当往后添加一个元素时,如何判断是否是重复元素,就是让start到end-1分别与end做判断,如果重复就跳过。
这样将set换成vector,同时剪枝就会使程序时间降低。

你可能感兴趣的:(leetcode,算法,字符串,数据结构,leetcode,算法)