题目;93.复制IP地址(切割回文子串加强版)(切到头冒烟)
尝试解答:
遇到问题:怎样保证切成了四组合法的?
怎样收集切割结果?
树型结构:
首先来实现一下判断本次截取的字符串是否合法的函数(左闭右闭区间):
bool isValid(const string& s, int start, int end){
if(start > end){ //怎么可能start > end?
return false;
}
if(s[start] == '0' && start != end){ // 0开头不合法
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;
}
终止条件如下:
if(pointSum == 3){ //加了三个逗点,说明现在只剩下了四段
if(isValid(s, startIndex, s.size() - 1)){
result.push_back(s);
}
}
回溯过程如下:
for(int i = startIndex; i < s.size(); i++){
if(isValid(s, startIndex, i)){ //判断[startIndex, i]区间是否合法
s.insert(s.begin() + i + 1, '.'); //合法则在i后面插逗点
}else{
break; //不合法,再向后切割也不可能合法,那就不用再向下递归了
}
pointSum++;
backtracking(s, i + 2, pointSum); //注意此处传入i + 2作为下一层的startIndex,因为插入了一个逗点
pointSum--;
s.erase(s.begin() + i + 1);
}
总体代码如下:本题没有像以前一样设置path来记录路径,而是直接在s上进行修改
class Solution {
private:
vector<string> result;
bool isValid(const string& s, int start, int end){
if(start > end){ //怎么可能start > end?
return false;
}
if(s[start] == '0' && start != end){ // 0开头不合法
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 pointSum){
if(pointSum == 3){ //加了三个逗点,说明现在只剩下了四段
// 判断第四段子字符串是否合法,如果合法就放进result中
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后面插逗点
}else{
break; //不合法,再向后切割也不可能合法,那就不用再向下递归了
}
pointSum++;
backtracking(s, i + 2, pointSum); //注意此处传入i + 2作为下一层的startIndex,因为插入了一个逗点
pointSum--;
s.erase(s.begin() + i + 1);
}
}
public:
vector<string> restoreIpAddresses(string s) {
backtracking(s, 0, 0);
return result;
}
};
补充:
1.const怎么用
2.insert和erase库函数?
3.public和private都是什么意思?
题目:78.子集
这道题和之前的组合有什么区别?甚至还要简单一些?
这道题的终止条件应该怎么设置?
""应该怎么装进result?
尝试写出入下代码:
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int startIndex){
result.push_back(path);
if(path[path.size() - 1] == nums[nums.size() - 1]){
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) {
backtracking(nums, 0);
return result;
}
};
思路:树型模型
如果把 子集问题、组合问题、分割问题都抽象为一棵树的话,那么组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点!
正确的终止条件应该是:
就是startIndex已经大于数组的长度了,就终止了,因为没有元素可取了,代码如下:
if (startIndex >= nums.size()) { return; }
其实可以不需要加终止条件,因为startIndex >= nums.size()(记住这个终止条件,会用于其他题),本层for循环本来也结束了。
正确代码入下:
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++){
path.push_back(nums[i]);
backtracking(nums, i + 1);
path.pop_back();
}
}
vector<vector<int>> subsets(vector<int>& nums) {
backtracking(nums, 0);
return result;
}
};
在注释中,可以发现可以不写终止条件,因为本来我们就要遍历整棵树。
有的同学可能担心不写终止条件会不会无限递归?
对比可知,删掉那自以为是的终止条件即可。为什么之前那个终止条件是错误的呢?是逻辑的问题还是语法的问题呢?
另外,’‘这个空集合是什么时候被加入result的呢?
因为result.push_back(path)放在了终止条件之前,所以在第一层递归的最开始,path就被记录了一次,此时path为空,问题解决!
题目:90.子集||
本题与上一题目78.子集的区别是本题中原来的集合中出现了重复的元素。
尝试解答:
根据之前所学,这道题应该需要数层去重吧,设置used数组,当nums[i] == nums[i - 1] && used[i - 1] == 0时,应该continue进行去重。
树型结构:
尝试成功,秒了!
代码如下:
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int startIndex, vector<bool>& used){
result.push_back(path);
if(startIndex == nums.size()) return;
for(int i = startIndex; i < nums.size(); i++){
if(i > 0 && nums[i] == nums[i - 1] && used[i - 1] == 0) 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) {
sort(nums.begin(), nums.end());
vector<bool> used(nums.size(), false);
backtracking(nums, 0, used);
return result;
}
};
通过本题对“树层去重”多了一些自己的理解:
当nums[i] == nums[i - 1]时,两个元素数值相同,说明他们在本层起到的作用,是等效的,意思就是说,你在本层取nums[i]也好,取nums[i - 1]也好,当你再往下递归,这层取的数反正都是一样大,所以只用取一遍就好。那到底是取前面的nums[i - 1]还是nums[i]呢?应该是nums[i - 1],因为经过浅浅的思考一下就可以想到,本层取num[i]得到的结果子集,是被包含于本层取nums[i - 1]得到的结果子集的。That't all.