面试题_回溯法
什么时候使用 used 数组,什么时候使用 begin 变量
可能会疑惑什么时候使用 used 数组,什么时候使用 begin 变量。简单总结一下:
排列问题,讲究顺序(即 [2, 2, 3] 与 [2, 3, 2] 视为不同列表时),需要记录哪些数字已经使用过,此时用 used 数组;
组合问题,不讲究顺序(即 [2, 2, 3] 与 [2, 3, 2] 视为相同列表时),需要按照某种顺序搜索,此时使用 begin 变量。
排列问题
46. 全排列
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
示例 1:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例 2:
输入:nums = [0,1]
输出:[[0,1],[1,0]]
示例 3:
输入:nums = [1]
输出:[[1]]
提示:
1 <= nums.length <= 6
-10 <= nums[i] <= 10
nums 中的所有整数 互不相同
class Solution {
vector<vector<int> > ans;
vector<int> t;
public:
void dfs(vector<int>& nums, int cur, int nlen, vector<int>& used)
{
if (cur == nlen) {
ans.push_back(t);
return;
}
for(int i = 0; i < nlen; ++i)
{
if (!used[i]) {
t.push_back(nums[i]);
used[i] = true;
dfs(nums, cur + 1, nlen, used);
t.pop_back();
used[i] = false;
}
}
}
vector<vector<int>> permute(vector<int>& nums) {
int nlen = nums.size();
vector<int> used(nlen);
dfs(nums, 0, nlen, used);
return ans;
}
};
class Solution {
public:
void dfs(vector<vector<int> >& ans, vector<int>& nums, int first, int nlen)
{
if (first == nlen) {
ans.emplace_back(nums);
return;
}
for(int i = first; i < nlen; ++i)
{
swap(nums[i], nums[first]);
dfs(ans, nums, first + 1, nlen);
swap(nums[i], nums[first]);
}
}
vector<vector<int>> permute(vector<int>& nums)
{
vector<vector<int> > ans;
int nlen = nums.size();
dfs(ans, nums, 0, nlen);
return ans;
}
};
复杂度分析
- 时间复杂度:O(n×n!),其中 n 为序列的长度。
- 空间复杂度:O(n)
47. 全排列 II
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
示例 1:
输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]
示例 2:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
提示:
1 <= nums.length <= 8
-10 <= nums[i] <= 10
class Solution {
vector<int> vis;
public:
void dfs(vector<vector<int> >& ans, vector<int>& nums, int cur, int nlen, vector<int>& perm)
{
if (cur == nlen) {
ans.emplace_back(perm);
return;
}
for (int i = 0; i < nlen; ++i)
{
if(vis[i] || (i > 0 && nums[i - 1] == nums[i] && vis[i - 1])) {
continue;
}
perm.emplace_back(nums[i]);
vis[i] = 1;
dfs(ans, nums, cur + 1, nlen, perm);
vis[i] = 0;
perm.pop_back();
}
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<int> perm;
vector<vector<int> > ans;
int nlen = nums.size();
vis.resize(nlen);
sort(nums.begin(), nums.end());
dfs(ans, nums, 0, nlen, perm);
return ans;
}
};
复杂度分析
- 时间复杂度:O(n×n!),其中 n 为序列的长度。
- 空间复杂度:O(n)
法二:下一个排列(最优)
class Solution {
public:
bool nextpermute(vector<int>& nums)
{
int i = nums.size() - 2;
while (i >= 0 && nums[i] >= nums[i + 1]) i--;
if (i < 0) return false;
int j = nums.size() - 1;
while (j >= 0 && nums[i] >= nums[j]) j--;
if (j < 0) return false;
swap(nums[i], nums[j]);
reverse(nums.begin() + i + 1, nums.end());
return true;
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<vector<int> > ans;
sort(nums.begin(), nums.end());
do {
ans.emplace_back(nums);
} while (nextpermute(nums));
return ans;
}
};
时间复杂度:O(n!n)
空间复杂度: O(1)
剑指 Offer 38. 字符串的排列
输入一个字符串,打印出该字符串中字符的所有排列。
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
示例:
输入:s = "abc"
输出:["abc","acb","bac","bca","cab","cba"]
限制:
1 <= s 的长度 <= 8
class Solution {
vector<int> vis;
public:
void dfs(vector<string>& ans, const string& s, int cur, int slen, string& perm)
{
if(cur == slen)
{
ans.emplace_back(perm);
return;
}
for (int i = 0; i < slen; ++i)
{
if(vis[i] || (i > 0 && s[i] == s[i - 1] && vis[i - 1])) {
continue;
}
perm.push_back(s[i]);
vis[i] = 1;
dfs(ans, s , cur + 1, slen, perm);
vis[i] = 0;
perm.pop_back();
}
}
vector<string> permutation(string s) {
int slen = s.size();
vis.resize(slen);
vector<string> ans;
string perm;
sort(s.begin(), s.end());
dfs(ans, s, 0, slen, perm);
return ans;
}
};
时间复杂度:O(n!n)
空间复杂度: O(n)
方法二:用set去重
class Solution {
vector<string> ans;
public:
void dfs(string& s, int cur, int slen)
{
if (cur == slen - 1) {
ans.emplace_back(s);
return;
}
unordered_set<int> uset;
for(int i = cur; i < slen; i++)
{
if (uset.find(s[i]) != uset.end()) {
continue;
}
uset.insert(s[i]);
swap(s[i], s[cur]);
dfs(s, cur + 1, slen);
swap(s[i], s[cur]);
}
}
vector<string> permutation(string s) {
dfs(s, 0, s.length());
return ans;
}
};
时间复杂度:O(n!n)
空间复杂度: O(n2)
方法三:下一个排列(最优)
class Solution {
public:
bool nextPermutation(string& s) {
int i = s.size() - 2;
while (i >= 0 && s[i] >= s[i + 1]) {
i--;
}
if (i < 0) {
return false;
}
int j = s.size() - 1;
while (j >= 0 && s[i] >= s[j]) {
j--;
}
swap(s[i], s[j]);
reverse(s.begin() + i + 1, s.end());
return true;
}
vector<string> permutation(string s) {
vector<string> ret;
sort(s.begin(), s.end());
do {
ret.push_back(s);
} while (nextPermutation(s));
return ret;
}
};
时间复杂度:O(n!n)
空间复杂度: O(1)
组合问题
39. 组合总和
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。 对于给定的输入,保证和为 target 的不同组合数少于 150 个。
示例 1:
输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。
示例 2:
输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]
示例 3:
输入: candidates = [2], target = 1
输出: []
提示:
1 <= candidates.length <= 30
1 <= candidates[i] <= 200
candidate 中的每个元素都 互不相同
1 <= target <= 500
class Solution {
int cur_num;
public:
void dfs(vector<vector<int> >& ans, vector<int>& candidates, vector<int>& t, int target, int begin, int nlen)
{
if (target < 0) {
return;
}
if (target == 0) {
ans.emplace_back(t);
return;
}
for(int i = begin; i < nlen; ++i)
{
t.emplace_back(candidates[i]);
// 组合问题,不讲究顺序(即 [2, 2, 3] 与 [2, 3, 2] 视为相同列表时)
// 需要按照某种顺序搜索,此时使用 begin 变量。
dfs(ans, candidates, t, target - candidates[i], i, nlen);
t.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
int nlen = candidates.size();
vector<int> t;
vector<vector<int> > ans;
dfs(ans, candidates, t, target, 0, nlen);
return ans;
}
};
40. 组合总和 II
给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]
示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
输出:
[
[1,2,2],
[5]
]
提示:
1 <= candidates.length <= 100
1 <= candidates[i] <= 50
1 <= target <= 30
使用begin变量
class Solution {
public:
void dfs(vector<vector<int> >& ans, vector<int>& candidates, vector<int>& perm, int target, int begin, int clen)
{
if (target == 0) {
ans.emplace_back(perm);
return;
}
if (target < 0) {
return;
}
for(int i = begin; i < clen; ++i)
{
if (target - candidates[i] < 0) {
break;
}
if ((i > begin && candidates[i] == candidates[i - 1])) {
continue;
}
perm.emplace_back(candidates[i]);
dfs(ans, candidates, perm, target - candidates[i], i + 1, clen);
perm.pop_back();
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
int clen = candidates.size();
vector<vector<int> > ans;
vector<int> perm;
sort(candidates.begin(), candidates.end());
dfs(ans, candidates, perm, target, 0, clen);
return ans;
}
};
78. 子集
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0]
输出:[[],[0]]
提示:
1 <= nums.length <= 10
-10 <= nums[i] <= 10
nums 中的所有元素 互不相同
class Solution {
public:
vector<int> t;
vector<vector<int> > ans;
void dfs(vector<int>& nums, int cur)
{
if (cur == nums.size()) {
ans.push_back(t);
return;
}
// 取cur, 需要回溯
t.push_back(nums[cur]);
dfs(nums, cur + 1);
t.pop_back();
// 不取cur
dfs(nums, cur + 1);
}
vector<vector<int>> subsets(vector<int>& nums) {
dfs(nums, 0);
return ans;
}
};
复杂度分析
时间复杂度:O(n×2n)。一共 2n 个状态,每种状态需要 O(n) 的时间来构造子集。
空间复杂度:O(n)。临时数组 t 的空间代价是 O(n),递归时栈空间的代价为 O(n)。
90. 子集 II
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
示例 1:
输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]
示例 2:
输入:nums = [0]
输出:[[],[0]]
提示:
1 <= nums.length <= 10
-10 <= nums[i] <= 10
class Solution {
public:
vector<int> t;
vector<vector<int> > ans;
void dfs(bool choosePre, int cur, vector<int>& nums)
{
if (cur == nums.size()) {
ans.push_back(t);
return;
}
// 不选
dfs(false, cur + 1, nums);
if (!choosePre && cur > 0 && nums[cur - 1] == nums[cur]) {
return;
}
// 选
t.push_back(nums[cur]);
dfs(true, cur + 1, nums);
t.pop_back();
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(), nums.end());
dfs(false, 0, nums);
return ans;
}
};