思路:
其实和组合问题思路类似,需要一个参数startIndex来作为分割线。
参数和返回值:参数是字符串s和startIndex,返回值是void
终止条件:当分割线来到了字符串末尾即startIndex == s.size(),说明满足分割回文串的条件,放进结果集里。判断是否满足回文串放在单层搜索逻辑里。
单层搜索逻辑:因为一上来就要对元素进行分割,所以先判断是否是回文串,是的话把字串放进path数组里,注意字串可以调用str.substr()函数收集,然后进入下一层递归再回溯。不是的话直接continue,跳过这次循环,进入下一次循环判断。
代码:
class Solution {
private:
vector<vector<string>> result;
vector<string> path; // 放已经回文的子串
void backtracking (const string& s, int startIndex) {
// 如果起始位置已经大于s的大小,说明已经找到了一组分割方案了
if (startIndex >= s.size()) {
result.push_back(path);
return;
}
for (int i = startIndex; i < s.size(); i++) {
if (isPalindrome(s, startIndex, i)) { // 是回文子串
// 获取[startIndex,i]在s中的子串
//s.substr(startpos,length)其中 startpos 是起始字符的序号,length 是[从 startpos 开始]取的字符串长度(包括startpos)。
//startIndex是起始位置,i - startIndex + 1是分割的字符串长度
string str = s.substr(startIndex, i - startIndex + 1);
path.push_back(str);
} else { // 不是回文,跳过
continue;
}
backtracking(s, i + 1); // 寻找i+1为起始位置的子串
path.pop_back(); // 回溯过程,弹出本次已经填在的子串
}
}
bool isPalindrome(const string& s, int start, int end) {
for (int i = start, j = end; i < j; i++, j--) {
if (s[i] != s[j]) {
return false;
}
}
return true;
}
public:
vector<vector<string>> partition(string s) {
result.clear();
path.clear();
backtracking(s, 0);
return result;
}
};
题目:93. 复原 IP 地址 - 力扣(LeetCode)
思路:
类似于分割回文串,注意参数不仅有startIndex定位起始位置也是分割位置,pointNum记录插入的点号(.) 数量。递归时,起始位置不再是i + 1,而是由于点号的插入,变为i + 2。
代码:
class Solution {
public:
vector<string> result;
bool IsValid(const string& s, int start, int end){
if(start > end) return false;
if(s[start] == '0' && start != end) return false;
int num = 0;
for (int i = start; i <= end; i++) {
if (s[i] > '9' || s[i] < '0') { // 遇到非数字字符不合法
return false;
}
num = num * 10 + (s[i] - '0');
if (num > 255) { // 如果大于255了不合法
return false;
}
}
return true;
}
// startIndex: 搜索的起始位置,pointNum:添加逗点的数量
void backtracking(string& s, int startIndex, int pointNum){
if(pointNum == 3){//逗号的数量为3时,分割结束
//判断第四段字符串是否合法,合法的话就放进结果集里
if(IsValid(s, startIndex, s.size() - 1)){
result.push_back(s);
}
return;
}
for(int i = startIndex; i < s.size(); i++){
if(IsValid(s, startIndex, i)){//判断[startIndex, i]区间字符串是否合法
s.insert(s.begin() + i + 1, '.');//在i后面插入一个点号
pointNum++;//点号数量加一
backtracking(s, i + 2, pointNum);//起始位置由于字符串中加了一个点号,所以不是i + 1,而是i + 2
pointNum--;//回溯点号数量
s.erase(s.begin() + i + 1);//回溯插入的点号
}else{
break;//如果不合法,直接结束本层循环
}
}
return;
}
vector<string> restoreIpAddresses(string s) {
result.clear();
if(s.size() < 4 || s.size() > 12) return result;
backtracking(s, 0, 0);
return result;
}
};
思路:组合和分割问题收集的是树上的叶子节点 ,子集问题收集的就是树上的所有节点。依然需要一个参数startIndex来定位for循环的遍历起始点,在终止条件之前就可以把path数组放进结果集里,也可以不要终止条件。
代码:
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int startIndex){
result.push_back(path);
for(int i = startIndex; i < nums.size(); i++){
path.push_back(nums[i]);
backtracking(nums, i + 1);
path.pop_back();
}
return;
}
vector<vector<int>> subsets(vector<int>& nums) {
result.clear();
path.clear();
backtracking(nums, 0);
return result;
}
};
思路:集合里有重复元素,因此需要对子集进行去重。本题和组合问题去重一样,注意先对集合进行排序,让相同的元素紧挨着一起,之后在 单层遍历逻辑里对处于同一树层的元素进行去重,去重逻辑是if(i > startIndex && nums[i] == nums[i - 1]) continue;
代码:
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int startIndex){
result.push_back(path);
if(startIndex >= nums.size()) return;
for(int i = startIndex; i < nums.size(); i++){
//对同一树层使用过的元素进行去重,注意i > startIndex,因为i = startIndex时,可以选取重复元素中的首元素,之后for循环遍历树层时,就跳过重复元素。
if(i > startIndex && nums[i] == nums[i - 1]){
continue;
}else{
path.push_back(nums[i]);
}
backtracking(nums, i + 1);
path.pop_back();
}
return;
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
result.clear();
path.clear();
sort(nums.begin(), nums.end());//去重先进行排序,让相同的元素紧挨着一起
backtracking(nums, 0);
return result;
}
};