力扣93、复原IP地址
注意:判断字符串是否合法有三点
- 当前字符串数字前有0不合法
- 当前字符串数字大于255不合法
- 当前字符串含有非正整数的字符不合法
主要函数思路(回溯):首先是函数返回值及参数,返回值是void,参数一个是题目所给的字符串,还需要一个startIndex确定切割线,这里和上一道题切割回文串中startIndex的作用是一致的,还需要一个pointNum表示逗点的数量,因为IP地址一定是有四个字符串三个逗点组成的;
第二步是确定递归的终止条件,当字符串中逗点的数量达到了三个,并且最后一个字符串是有效的(注意此时最后一个字符串还没有判断合法性),那么就将该字符串保存到结果集中,之前字符串的有效性由自定义函数实现;
第三步是确定单层递归的逻辑,单层递归的时候每次要先判断当前切割的字符串的有效性,主要考虑的就是前面注意中的三点,接着如果字符串不合法的话直接结束本层递归就可以了,如果字符串合法的话,那么就要在i的下一个位置(C++)添加逗点,并且让逗点的计数值加加,接着继续往下递归,判断后面的字符串是否合法,回溯的时候要做到跟递归一一对应,既要把逗点的计数值减减,也要把相应的逗点删除。
代码:
class Solution {
public:
vector<string> result;
void backtracking(string& s,int pointNum,int startIndex){//不能加const,加了const就不能使用插入和删除操作了
//startIndex记录开始搜索节点的位置 pointNum记录逗点的数量
//回溯的终止条件
if(pointNum==3){
//还需要判断最后一个字符串的合法性
if(isVaild(s,startIndex,s.size()-1)){
result.push_back(s);
return;
}
}
//单层递归逻辑
for(int i=startIndex;i<s.size();i++){
if(isVaild(s, startIndex, i)){
//在s的后面加入一个逗点
s.insert(s.begin()+i+1,'.');
pointNum++;
backtracking(s, pointNum, i+2);
pointNum--;
s.erase(s.begin()+i+1);
}else break;
}
}
//判断字符串的有效性
bool isVaild(const string& s,int start,int end){
if(start>end){
return false;
}
if(s[start]=='0'&&start!=end){
return false;//数字开头有0不合法
}
int num=0;
for(int i=start;i<=end;i++){//遇到end时也要判断,这里对字符串的判断是左闭右闭区间
if(s[i]>'9'||s[i]<'0'){
return false;//遇到非法数字就不合法
}
num=num*10+(s[i]-'0');
if(num>255){
return false;//遇到num>255就不合法
}
}
return true;
}
vector<string> restoreIpAddresses(string s) {
result.clear();
backtracking(s,0,0);
return result;
}
};
力扣78、子集问题
思路:子集问题和组合问题和切割问题最大的不同就是组合和切割研究的是树的叶子节点的问题,子集问题研究的是树的所有节点。
第一步,确定函数的参数和返回值,返回值是void,函数的参数需要题目提供的数组和startIndex起到避免重复子集的作用。
第二步,确定递归的终止条件,当startIndex遍历到大于等于数组集的长度的时候,结束递归,return
第三步,单层递归逻辑,因为要收集所有节点的情况,所以不需要加剪枝操作。
代码:
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()){
//如果startIndex已经到了数组的尺寸,说明剩余集合已经为空了
return;
}
//单层递归逻辑
//单层递归的时候必须要处理掉树中所有的节点情况,所以不用做剪枝操作
for(int i=startIndex;i<nums.size();i++){
path.push_back(nums[i]);
backtracking(nums, i+1);
path.pop_back();
}
}
vector<vector<int>> subsets(vector<int>& nums) {
result.clear();
path.clear();
backtracking(nums, 0);
return result;
}
};
回溯周末总结学完子集II后看一下
90、子集II
思路:本题相比较于上一题子集问题主要就是加了一个去重的操作,因为当i遍历到数组集的末尾时自动会结束递归,因此这里把单层递归的逻辑和递归终止条件放到一块处理了。
第一步确定函数的参数和返回值,函数的返回值是void,函数的参数有传入的数组集(加引用),还有startIndex用来表示下一层开始搜索的位置,此外还需要一个布尔型的数组的状态用来表示是同一树枝还是同一树层的元素已经使用过。
第二步直接写单层递归的逻辑了,去重的操作就是在这里进行的,这里去重的思路和组合问题II中去重的思路是一个路子的,都是要弄清楚是要同一树枝的元素不重复使用还是同一树层的元素不重复使用,本题是要求子集不重复,通过把问题简化为N叉树,并带入简单情况{1,2,2}可以发现是要对同一树层进行去重,因此,当i>0(主要只有此时才有可能出现重复的情况)并且nums[i]==nums[i-1]且布尔型数组判断是同一层的元素在重复使用时,我们就对当前层的循环跳过。
代码:
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
// 本题主要就是在子集的基础上加了一个去重的操作
void backtracking(vector<int>&nums,int startIndex,vector<bool>&used){
result.push_back(path);//保存的是所有的子集
for(int i=startIndex;i<nums.size();i++){
//used[i-1]==true表示同一树枝的元素使用过
//used[i-1]==false表示同一树层的元素使用过,需要去重
//这里当i遍历到数组末尾时,自动就会结束递归,所以把递归终止条件和单层递归逻辑合并了
if(i>0&&nums[i]==nums[i-1]&&used[i-1]==false){
continue;
}
path.push_back(nums[i]);
used[i]=true;
backtracking(nums, i+1, used);
used[i]=false;//用来做标记,防止当前元素在同一树层重复使用造成子集重复
path.pop_back();
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
result.clear();
path.clear();
sort(nums.begin(),nums.end());//对子集进行去重前要先排序
vector<bool>used(nums.size(),false);//开辟一个布尔型的数组,用来标记元素在同一树层是否重复使用,初始化为false
backtracking(nums,0,used);
return result;
}
};