回溯模板
场景:原问题查找子字符串问题转化为,已知分到i处(i为原字符串索引),剩余问题仍是一个找字串的原问题
解决问题需要的变量有:
s:原字符串
start:已经分到的位置
n:需要找的最终满足的条件
sum:逼近最终条件过程中的值
bool dfs(const string& s, int start, const int& n, int sum) {
if (start == s.size()) {
// 最终返回的条件,可以是false/true. 也可以是存储的路径
return n == sum;
}
for (int j = start; j < s.size(); j++) {
// start到新的分割点的中间结果
int cur = stoi(s.substr(start, j - start + 1));
sum += cur; // 结果的输入到下一轮的计算中
// 对结果的判断
if (dfs(s, j + 1, n, sum)) {
return true;
}
sum -= cur; // 回溯去除本轮的计算结果
}
return false;
}
排列 - 组合/子集
排列 - 无重复不可复选
void backtrack(vector<int>& nums) {
// base case,到达叶子节点
if (track.size() == nums.size()) {
// 收集叶子节点上的值
res.push_back(vector<int>(track.begin(), track.end()));
return;
}
// 回溯算法标准框架
for (int i = 0; i < nums.size(); i++) {
// 已经存在 track 中的元素,不能重复选择
if (used[i]) {
continue;
}
// 做选择
used[i] = true;
track.push_back(nums[i]);
// 进入下一层回溯树
backtrack(nums);
// 取消选择
track.pop_back();
used[i] = false;
}
}
排列 - 可重复不可复选
需要注意提前排序,注意减枝
sort(nums.begin(), nums.end());
void backtrack(vector<int>& nums) {
// 当长度相等时,将结果记录
if (track.size() == nums.size()) {
res.push_back(track);
return;
}
// 遍历没有被使用过的元素
for (int i = 0; i < nums.size(); i++) {
if (used[i]) {
continue;
}
// 新添加的剪枝逻辑,固定相同的元素在排列中的相对位置
if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {
continue;
}
// 添加元素,标记为使用过
track.push_back(nums[i]);
used[i] = true;
// 继续回溯
backtrack(nums);
// 回溯
track.pop_back();
used[i] = false;
}
}
子集- 无重复不可复选
void backtrack(vector<int>& nums, int start) {
// 前序位置,每个节点的值都是一个子集
res.push_back(track);
// 回溯算法标准框架
for (int i = start; i < nums.size(); i++) {
// 做选择
track.push_back(nums[i]);
// 通过 start 参数控制树枝的遍历,避免产生重复的子集
backtrack(nums, i + 1);
// 撤销选择
track.pop_back();
}
}
组合/子集 - 可重复不可复选(*)
需要注意提前排序,注意减枝
sort(nums.begin(), nums.end());
void backtrack(vector<int>& nums, int start) { // start 为当前的枚举位置
res.emplace_back(track); // 前序位置,每个节点的值都是一个子集
for(int i = start; i < nums.size(); i++) {
if (i > start && nums[i] == nums[i - 1]) { // 剪枝逻辑,值相同的相邻树枝,只遍历第一条
continue;
}
track.emplace_back(nums[i]); // 添加至路径
backtrack(nums, i + 1); // 进入下一层决策树
track.pop_back(); // 回溯
}
}
Leetcode用例
131: 分割回文串
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
示例 1:
输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]
示例 2:
输入:s = "a"
输出:[["a"]]
提示:
1 <= s.length <= 16
s 仅由小写英文字母组成
class Solution {
public:
vector<vector<string>> path;
vector<string> ans;
void dfs(const string& s, int start) {
if (start == s.size()) {
path.push_back(ans);
return ;
}
for (int j = start; j < s.size(); j++) {
string temp = s.substr(start, j - start + 1);
if (temp == string(temp.rbegin(), temp.rend())) {
ans.push_back(temp);
dfs(s, j + 1);
ans.pop_back();
}
}
}
vector<vector<string>> partition(string s) {
dfs(s, 0);
return path;
}
};
6441:求一个整数的惩罚数
给你一个正整数 n ,请你返回 n 的 惩罚数 。 n 的 惩罚数 定义为所有满足以下条件 i 的数的平方和:
说明: 1 <= i <= n i * i 的十进制表示的字符串可以分割成若干连续子字符串,且这些子字符串对应的整数值之和等于 i 。
示例1:
输入:n = 37
输出:1478
解释:总共有 4 个整数 i 满足要求:
- 1 ,因为 1 * 1 = 1
- 9 ,因为 9 * 9 = 81 ,且 81 可以分割成 8 + 1 。
- 10 ,因为 10 * 10 = 100 ,且 100 可以分割成 10 + 0 。
- 36 ,因为 36 * 36 = 1296 ,且 1296 可以分割成 1 + 29 + 6 。 因此,37 的惩罚数为 1 + 81 + 100 + 1296 = 1478
提示:
1 <= n <= 1000
class Solution {
public:
bool dfs(const string& s, int start, const int& i, int sum) {
if (start == s.size()) {
return i == sum;
}
for (int j = start; j < s.size(); j++) {
int temp = stoi(s.substr(start, j - start + 1));
sum += temp;
if (sum > i) {
break;
}
if (dfs(s, j + 1, i, sum)) {
return true;
}
sum -= temp;
}
return false;
}
bool conditionNumber(const int i) {
if (i * i == i) {
return true;
}
string s = to_string(i * i);
return dfs(s, 0, i, 0);
}
int punishmentNumber(int n) {
int count = 0;
for (size_t i = 1; i <=n ; i++) {
if (conditionNumber(i)) {
count += i * i;
}
}
return count;
}
};
DEBUG
前提:
void printDebug(int n) {
for (int i = 0; i < n; i++) {
std::cout << " ";
}
}
使用方法:
1、初始化全局变量int n = 0;
2、在函数开始处添加打印,且需要添加换行符
printDebug(n++);
std::cout << std::endl;
3、在函数return前添加打印,且需要添加换行符
printDebug(--n);
std::cout << std::endl;
例如:以下代码去掉注释行