40. 组合总和 II
这道题跟上篇文章的39题差不多,就是要多解决一个问题——去重。如果是将所有答案都存储在容器中之后再去重,那就比较耗时。这里要想办法在遍历的过程中去重。可以将遍历的过程看作是在遍历一棵树,当确立了根节点,其余可以取的值就都是该根节点的孩子结点。去重的操作就是保证同层的结点的值不能重复,但是在保证每个元素只取一次的前提下同一个路径(从根节点到某一叶子结点)的元素是可以重复的。
同层重复的元素不可取,同枝重复的元素可取。
!!!【错误的代码】
可能好多人刚开始会这样写,我也是
for(int i=startIndex;i0 && candidates[i]==candidates[i-1]) continue;
//放进路径中
path.push_back(candidates[i]);
sum+=candidates[i];
//在下一层寻找值
backtracking(candidates,target,i+1);
//返回上一层前,弹出当前值
sum-=candidates[i];
path.pop_back();
}
这样写无法判断当前元素与上一个元素是同层还是同枝,会把同层同枝中相同的元素全部舍弃,会遗漏部分组合。
那么现在的问题就是如果当前取的元素和上一个元素是重复的,该如何判断该元素与上一个元素是同层还是同枝呢?
第一种方法是创建一个bool类型的数组,当取了第i个值时,就将第i个值设为true。如果前一个值也为true,说明它被上一层取了,那么当前值与前一个值同枝,即使重复也没有关系。如果前一个值为false,那说明上一层没取它,那么当前值与前一个值一定同层。这点不好理解,我们可以看看上图,如果我第一个值取1,第二个值取了2,这时2的前一个值1对应的是false,1和2同层。
public:
vector path;
vector> ans;
int sum=0;
void backtracking(vector candidates,int target,int startIndex,vector used)
{
if(sum==target)
{
ans.push_back(path);
return;
}
//这里多加了个条件sum+candidates[i]<=target,其实也可以这里不写,在上面if前加if(sum>target) return
//但是在这里加条件效率更高,就不需要存取i的值,不需要递归到下一层
for(int i=startIndex;i0 && candidates[i]==candidates[i-1])
{
if(used[i-1]==false) continue;
}
path.push_back(candidates[i]);
sum+=candidates[i];
used[i]=true;
backtracking(candidates,target,i+1,used);
sum-=candidates[i];
path.pop_back();
used[i]=false;
}
}
vector> combinationSum2(vector& candidates, int target) {
vector used(candidates.size(),false);
sort(candidates.begin(),candidates.end());
backtracking(candidates,target,0,used);
return ans;
}
};
第二种方法是通过startIndex来判断,
如果当前i的值大于startIndex说明当前值一定与前一个值同层。通过画图就很容易理解了。
所有只需要当i>startIndex时,判断i与前一个值是否相等,如果相等就舍弃当前值,直接遍历下一个值。
class Solution {
public:
vector path;
vector> ans;
int sum=0;
void backtracking(vector candidates,int target,int startIndex)
{
if(sum==target)
{
ans.push_back(path);
return;
}
//这里多加了个条件sum+candidates[i]<=target,其实也可以这里不写,在上面if前加if(sum>target) return
//但是在这里加条件效率更高,就不需要存取i的值,不需要递归到下一层
for(int i=startIndex;istartIndex && candidates[i]==candidates[i-1]) continue;
//放进路径中
path.push_back(candidates[i]);
sum+=candidates[i];
//在下一层寻找值
backtracking(candidates,target,i+1);
//返回上一层前,弹出当前值
sum-=candidates[i];
path.pop_back();
}
}
vector> combinationSum2(vector& candidates, int target) {
sort(candidates.begin(),candidates.end());
backtracking(candidates,target,0);
return ans;
}
};
131. 分割回文串
做这道题的时候不要把问题想的那么复杂,其实这道题只需要解决应该在哪个地方对字符串进行切割,这需要理清楚切割的逻辑。
切割逻辑:从字符串起始位置开始切割,如果切割出的子串是回文串就将它放入路径中,如果切割出的子串不是回文串,我们就循环到下一个位置继续切割,直到找出回文子串将它放到路径中。
接下来就是在剩余的字符串中继续从起始位置切割,切割出回文子串,然后不断递归。
!!!重点是寻找切割点
class Solution {
public:
vector path;
vector> ans;
//判断是否为回文串
bool ispal(string s)
{
if(s.size()==0) return false;
int left=0,right=s.size()-1;
while(left> partition(string s) {
backtracking(s);
return ans;
}
};
93. 复原 IP 地址
这道题和上一题差不多,需要解决的问题就是寻找切割点,判断切割出来的数字是否有效。
还是横向遍历每个切割点,对每个切割点加一判断,符合条件就加入path中。不符合就继续循环下一个切割点。递归+回溯。
结束条件:path中有四个整数,且s中的字符串已经被切割完。
class Solution {
public:
vector path;
vector ans;
//判断字符串是否有效
bool isEffective(string s)
{
if(s=="") return false;
int size=s.size(),sum=0;
if(size!=1 && s[0]=='0') return false;
switch(size)
{
case 1:
sum=s[0]-'0';
break;
case 2:
sum=(s[0]-'0')*10+s[1]-'0';
break;
case 3:
sum=(s[0]-'0')*100+(s[1]-'0')*10+s[2]-'0';
break;
}
if(sum>=0&&sum<=255) return true;
return false;
}
//回溯
void backtracking(string s)
{
if(path.size()>4) return;
//结束条件,s被切割完且path中有四个整数
if(s.size()==0 && path.size()==4)
{
string temp=path[0]+'.'+path[1]+'.'+path[2]+'.'+path[3];
ans.push_back(temp);
return;
}
for(int i=0;i2) break;
string temp=s.substr(0,i+1);
//如果有效,继续递归下一层寻找有效值
if(isEffective(temp))
{
path.push_back(temp);
backtracking(s.substr(i+1,s.size()));
path.pop_back();
}
}
}
vector restoreIpAddresses(string s) {
backtracking(s);
return ans;
}
};