[剑指Offer]:字符串的排列(回溯)

84 阅读2分钟

文章目录


题目描述

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

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

示例:

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

题解思路

递归回溯

假设s = “abc”,那么我们首先让 a 在第 0 位,再让 b 在第 0 位,再让 c 在第 0 位。

  1. a 在第 0 位,那么还剩 bc。我们先放 b 在第 1 位,再让 c 在第 1 位。
  • b 在第 1 位,那么还剩 c。最终结果:abc。
  • c 在第 1 位,那么还剩 b。最终结果:acb。
  1. b 在第 0 位,那么还剩 ac。我们先放 a 在第 1 位,再让 c 在第 1 位。
  • a 在第 1 位,那么还剩 c。最终结果:bac。
  • c 在第 1 位,那么还剩 a。最终结果:bca。
  1. c 在第 0 位,那么还剩 ba。我们先放 b 在第 1 位,再让 a 在第 1 位。
  • b 在第 1 位,那么还剩 c。最终结果:cba。
  • a 在第 1 位,那么还剩 a。最终结果:cab。
  1. 同时,每一次的递归我们都维护一个 haseset,用于剪枝去重(比如 s = “abb”,肯定会出现重复的排列,没有必要多去计算)。

代码实现:

class Solution {
public:
    vector<string> permutation(string s) {
        vector<string> res;
        dfs(s, res, 0); // 从 s 的第一位开始排列起,所以传了 0
        return res;
    }
    void dfs(string &s, vector<string> &res, int depth)
    {
        if(depth >= s.size()-1)
        {
            res.push_back(s);   // 某条路从头排列到尾了,把这条路的结果输入 res
            return ;
        }
        unordered_set<char> used;  // 用于剪枝,不能定义为全局,因为每次递归需要创建一个新的 hashset
        for(int i = depth; i < s.size(); ++i)
        {
            if(used.count(s[i])) continue;  // 如果是重复的元素,不要再排一次
            
            used.insert(s[i]);
            swap(s[depth],s[i]);
            dfs(s, res, depth+1);   // 开始往下一层递进
            swap(s[depth],s[i]);    // 回溯撤销操作
        }
    }
};