回溯法的运用具备一定难度,要求同学对递归的原理有所了解,目前在日常练习的过程中遇到两个经典题目,记录如下:
一、括号生成
数字 n
代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例 1:
输入:n = 3 输出:["((()))","(()())","(())()","()(())","()()()"]
示例 2:
输入:n = 1 输出:["()"]
代码如下,附注释:
class Solution {
public:
vector<string> v; // 保存结果
void generate(string s, int left, int right) { // left/right分别表示还能生成的左/右括号数目
if (!left && !right) { // 递归下界
v.push_back(s);
return;
}
if (left > 0) { // 若左括号还能生成
s += '('; // 生成左括号
generate(s, left - 1, right); // 左括号数目-1,进入下一状态
s.erase(s.size() - 1, 1); // 回溯,回到不生成该左括号的状态
}
if (left < right) { // 若左括号能生成数目少于右括号能生成的数目,当前序列'('多于')'
s += ')'; // 生成右括号
generate(s, left, right - 1); // 进入下一状态
}
}
vector<string> generateParenthesis(int n) {
if (n <= 0)
return v;
generate("", n, n);
return v;
}
};
二、子集
给你一个整数数组 nums
,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3] 输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0] 输出:[[],[0]]
代码如下,附注释:
class Solution {
public:
vector<int> v; // 单个子集
vector<vector<int>> ans; // 子集结果
void dfs(int cur, vector<int>& nums) { // cur:当前搜索的数组位置
if (cur == nums.size()) { // 递归下界
ans.push_back(v); // 保存这一趟的结果
return;
}
v.push_back(nums[cur]); // 加入当前元素
dfs(cur + 1, nums); // 进入下一状态
v.pop_back(); // 回溯,回退到未加入当前元素的状态
dfs(cur + 1, nums); // 进入下一状态
}
vector<vector<int>> subsets(vector<int>& nums) {
dfs(0, nums);
return ans;
}
};
所谓回溯,其理解的重点在于:在操作系统进行递归的时候,会将当前函数的各个参数状态保存到栈中并进入下一状态,并且每一递归分支之间同源但互不影响。