剑指 Offer(第2版)面试题 38:字符串的排列
- 剑指 Offer(第2版)面试题 38:字符串的排列
- 解法1:回溯
- 扩展题一:LeetCode 46. 全排列
- 扩展题二:LeetCode 47. 全排列 II
剑指 Offer(第2版)面试题 38:字符串的排列
题目:输入一个字符串,打印出该字符串中字符的所有排列。
解法1:回溯
全排列问题,且各个字符都不相同。
代码:
void Permutation(char *pStr)
{if (pStr == nullptr)return;Permutation(pStr, pStr);
}void Permutation(char *pStr, char *pBegin)
{if (*pBegin == '\0')printf("%s\n", pStr);for (char *pCh = pBegin; *pCh != '\0'; pCh++){swap(*pCh, *pBegin);Permutation(pStr, pBegin + 1);swap(*pCh, *pBegin);}
}
复杂度分析:
时间复杂度:O(n × n!),其中 n 是序列的长度。回溯的调用次数是 O(n!),对于每个回溯的叶子节点,我们需要将当前答案使用 O(n) 的时间复制到答案数组中,相乘得时间复杂度为 O(n × n!)。
空间复杂度:O(n),其中 n 是序列的长度。除答案数组以外,递归函数在递归过程中需要为每一层递归函数分配栈空间,所以这里需要额外的空间且该空间取决于递归的深度,这里可知递归调用深度为 O(n)。
扩展题一:LeetCode 46. 全排列
题目来源:LeetCode 46. 全排列
给定一个没有重复数字的序列,返回其所有可能的全排列。
代码:
/** @lc app=leetcode.cn id=46 lang=cpp** [46] 全排列*/// @lc code=start
class Solution
{
public:// 主函数vector<vector<int>> permute(vector<int> &nums){vector<vector<int>> ans;backtrack(nums, 0, ans);return ans;}// 辅函数void backtrack(vector<int> &nums, int level, vector<vector<int>> &ans){if (level == nums.size() - 1){ans.push_back(nums);return;}for (int i = level; i < nums.size(); i++){swap(nums[i], nums[level]); // 修改当前节点状态backtrack(nums, level + 1, ans); // 递归子节点swap(nums[i], nums[level]); // 回改当前节点状态}}
};
// @lc code=end
复杂度分析:
时间复杂度:O(n × n!),其中 n 是数组 nums 的长度。回溯的调用次数是 O(n!),对于每个回溯的叶子节点,我们需要将当前答案使用 O(n) 的时间复制到答案数组中,相乘得时间复杂度为 O(n × n!)。
空间复杂度:O(n),其中 n 是数组 nums 的长度。除答案数组以外,递归函数在递归过程中需要为每一层递归函数分配栈空间,所以这里需要额外的空间且该空间取决于递归的深度,这里可知递归调用深度为 O(n)。
扩展题二:LeetCode 47. 全排列 II
题目来源:LeetCode 47. 全排列 II
序列中有重复的数字,要考虑重复,需要剪枝。
代码:
/** @lc app=leetcode.cn id=47 lang=cpp** [47] 全排列 II*/// @lc code=start
class Solution
{
private:vector<bool> visited;public:// 主函数vector<vector<int>> permuteUnique(vector<int> &nums){vector<vector<int>> ans;vector<int> seq;visited = vector<bool>(nums.size(), false);sort(nums.begin(), nums.end());backtrack(nums, 0, ans, seq);return ans;}// 辅函数void backtrack(vector<int> &nums, int level, vector<vector<int>> &ans, vector<int> &seq){if (level == nums.size()){ans.emplace_back(seq);return;}for (size_t i = 0; i < nums.size(); i++){if (visited[i] || (i > 0 && nums[i] == nums[i - 1] && !visited[i - 1])){continue;}seq.emplace_back(nums[i]);visited[i] = true;backtrack(nums, level + 1, ans, seq);visited[i] = false;seq.pop_back();}}
};
// @lc code=end
复杂度分析:
时间复杂度:O(n × n!),其中 n 是数组 nums 的长度。回溯的调用次数是 O(n!),对于每个回溯的叶子节点,我们需要将当前答案使用 O(n) 的时间复制到答案数组中,相乘得时间复杂度为 O(n × n!)。
空间复杂度:O(n),其中 n 是数组 nums 的长度。我们需要 O(n) 的标记数组,同时在递归的时候栈深度会达到 O(n),因此总空间复杂度为 O(n+n)=O(2n)=O(n)。