day27
1.组合总和
定义satrt标志为总和相加的开始,此后只需要不断的判断&target的值是否为0,如果为0说明此时的v数组是有效的,所以需要push到ret中。此外将每次的元素i一并push到v中之前需要判断当前的target值是否为小于0,小于则说明此时的v数组已经失效了,不管后续的i是否存在都不应该再继续push了。
class Solution {
public:
void _combinationSumR(vector<vector<int>>& ret, vector<int>& v, vector<int>& candidates, int& target, int start)
{
if (target == 0)
{
ret.push_back(v);
return;
}
for (int i = start; i < candidates.size(); i++)
{
if (target < 0)
break;
v.push_back(candidates[i]);
target -= candidates[i];
_combinationSumR(ret, v, candidates, target, i);
target += candidates[i];
v.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> ret;
vector<int> v;
_combinationSumR(ret, v, candidates, target, 0);
return ret;
}
};
2.组合总和 II
1.最开始,我们为了不出现由于顺序问题出现的重复问题,所以我们需要在进行组合之前,需要对元素进行排序,这样就不会出现由于顺序出现的重复了。
2.不过这一题由于其可以出现重复的值,所以结果会如果按照之前的写,会出现数值重复导致的组合重复。因此需要减枝。假设candidates = [1,1,1,2] target=3,如果不减枝,会出现三次[1,2]。分析为什么会这样,如果按照之前的写法,数组元素是不会重复,那么我们原先的start为i+1即可满足,但是,重复出现意味着i+1也会出现一样的数,那么此时就出现反复的组合了。那么我们需要在条件前加一个特征,分析直到其实我们需要一次重复元素对应的组合,那么如果i为就是start开始位置,说明此时就是第一次的组合。那么如果i>start,并且candidates[i]==candidates[i-1],说明此时的元素将要组合出来的数组在之前就出现过一次了,那么我们直接放弃这一次的循环,不push数组中i对应的元素,继续下一次循环。
class Solution {
public:
void _combinationSum2R(vector<vector<int>>& ret, vector<int>& v, vector<int>& candidates, int& target, int start)
{
if (target == 0)
{
ret.push_back(v);
return;
}
for (int i = start; i < candidates.size(); i++)
{
if(target<0)
break;
if (i > start && candidates[i] == candidates[i - 1])
continue;
v.push_back(candidates[i]);
target -= candidates[i];
_combinationSum2R(ret, v, candidates, target, i + 1);
v.pop_back();
target += candidates[i];
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
vector<vector<int>> ret;
vector<int> v;
_combinationSum2R(ret, v, candidates, target, 0);
return ret;
}
};
3.分割回文串
1.由于收集的是回文,那么我们此时需要一个bool函数判断是否回文
2.我们在递归中判断是否回文,来决定我们是否将收获的字符串收获到ret中。这样做减少判断,如果在收获到全排列的字符串后再进行回文判断会出现许多的多余问题。
3.如果是回文,则push到v中,如果不是回文,则将循环结束
class Solution {
public:
bool ispalindrome(string& word,int begin,int end)
{
while(begin<end)
{
if(word[begin++]!=word[end--])
return false;
}
return true;
}
void _partitionR(vector<vector<string>>& ret,vector<string> v,string& s,int start)
{
if(start==s.size())
{
ret.push_back(v);
}
for(int i=start;i<s.size();i++)
{
if(ispalindrome(s,start,i))
{
string str(s.begin()+start,s.begin()+i+1);
v.push_back(str);
}
else
continue;
_partitionR(ret,v,s,i+1);
v.pop_back();
}
}
vector<vector<string>> partition(string s) {
vector<vector<string>> ret;
vector<string> v;
_partitionR(ret,v,s,0);
return ret;
}
};
day28
1.复原IP地址
1.IP的两个点之间的格式有要求,所以我们需要写一个函数来判断这个格式的问题。首先我们定义函数,传入的传输为&s表示题目给的字符串,begin和end为两点之间的数据。如果begin>end,此时数据无效,返回false;如果begin不等于end且s[begin]==‘0’,此时为0xx的格式,这种格式是错误的,因此无效,返回false;筛选begin和end间的字符,如果有不在‘0’~‘9’的说明IP无效,返回false;如果最后的值>255,也不符合IP要求,返回false。其余的返回true即可。
2.其中的回溯思路和之前的几题差不多,需要注意的是我们只需要插入三个点,那么我们就传入&num定义为每层的点数,终止条件为num==3,此外我们要插入IP字符串,按道理我们是已经走过回溯的算法,得到前面都是合格的格式,那么现在就是最后没有被检查,所以我们只需要加一个条件isIPnum(s, start, s.size() - 1),判断start位置和end-1位置的格式是否合格即可判断是否要插入
3.最后处理如何插入点,我们只需要调用insert,在s的begin()+i+1位置进行插入点即可;回溯的思想就是将点重新删除,那么就是s.erase(s.begin() + i + 1)即可。
class Solution {
public:
bool isIPnum(string& s, int begin, int end)
{
if (begin > end)
return false;
if (begin != end && s[begin] == '0')
return false;
int num = 0;
for (int i = begin; i <= end; i++)
{
if (s[i] > '9' || s[i] < '0')
return false;
num = num * 10 + (s[i] - '0');
if (num > 255)
return false;
}
return true;
}
void _restoreIpAddressesR(vector<string>& ret, string& s, int start, int& num)
{
if (num == 3)
{
if (isIPnum(s, start, s.size() - 1))
ret.push_back(s);
return;
}
for (int i = start; i < s.size(); i++)
{
if (isIPnum(s, start, i))
{
s.insert(s.begin() + i + 1, '.');
_restoreIpAddressesR(ret, s, i + 2, ++num);
--num;
s.erase(s.begin() + i + 1);
}
else
break;
}
}
vector<string> restoreIpAddresses(string s) {
vector<string> ret;
int num = 0;
string buffer;
_restoreIpAddressesR(ret, s, 0, num);
return ret;
}
};
2.子集
收集每一个排列的元素,其次需要注意的就是空集也是子集,那么我们在进行回溯函数之前就可以先插入空集。
class Solution {
public:
void _subsetsR(vector<vector<int>>& ret,vector<int>& tmp,vector<int>& nums, int start)
{
if(start==nums.size())
return;
for(int i = start;i<nums.size();i++)
{
tmp.push_back(nums[i]);
ret.push_back(tmp);
_subsetsR(ret,tmp,nums,i+1);
tmp.pop_back();
}
}
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> ret;
vector<int> tmp;
ret.push_back(tmp);
_subsetsR(ret,tmp,nums,0);
return ret;
}
};
3.子集II
与上一题的思路基本一致,需要注意的只有两点:
1.该数组需要先经过排列,减去重复的分支
2.由于可出现重复的元素,因此子集按照上面的代码会出现重复,那么我们在插入开始前,需要将i!=start&&nums[i]==nums[i-1]判断,如果判断为真,说明此时前一个的元素和现在的元素相同,但是前一个元素已经将现在的元素组合的子集包含进去了,所以就不需要再进行组合,直接continue即可。
class Solution {
public:
void _subsetsR(vector<vector<int>>& ret,vector<int>& tmp,vector<int>& nums, int start)
{
if(start==nums.size())
return;
for(int i = start;i<nums.size();i++)
{
if(i!=start&&nums[i]==nums[i-1])
continue;
tmp.push_back(nums[i]);
ret.push_back(tmp);
_subsetsR(ret,tmp,nums,i+1);
tmp.pop_back();
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(),nums.end());
vector<vector<int>> ret;
vector<int> tmp;
ret.push_back(tmp);
_subsetsR(ret,tmp,nums,0);
return ret;
}
};