昨天切割回文子串的题目遇到了一些困难(分割问题怎么样放板子),今天的另外一个切割问题和子集问题开冲!!!
今日任务:
- 93.复原IP地址
- 78.子集
- 90.子集II
题目一:93.复原IP地址
Leetcode题目:【93.复原IP地址】
参考:【代码随想录之复原IP地址】
其树形结构如下:
关键问题:
(1)如何获取切割的字串;(2)小数点是怎样表示出来的呢?
startIndex来告诉我们如何从剩下的字符串中进行切割,递归时会用到;
startIndex就是我们的切割线
终止条件:逗点的个数为3(比较新颖的),加了逗点之后,就要对前面的合法性进行判断。因为3个逗号对应着4个字段,所以在终止条件的时候还需要对最后一个字串进行判断。
采用了子集的方法进行修改
方法一:重新定义path,然后在终止条件处进行调整
class Solution {
public:
vector<string> result;
vector<string> path; // 判断切割后的单个字符是否合法
string temp;
bool isValue(string s){
// 1. 首字符不为0; 2. 数字大小是位于0-255之间; 3.不能有特殊字符;
if(s.size() >1 && s[0] == '0') return false;
int num = stoi(s);
if(num < 0 || num > 255) return false;
for(int i = 0; i<s.size(); i++){
if(s[i]<'0' || s[i] > '9'){
return false;
}
}
return true;
}
void backtracking(string s, int startIndex, int pointnum){
// 终止条件
if(pointnum == 4){
// 分成4段,需要检查最后一段是否合法
if(isValue(path.back())){
for(int i = 0; i<path.size(); i++){
if( i == 3){
temp += path[i];
}else{
temp += path[i] + '.';
}
}
if(s.size() == temp.size()-3){
result.push_back(temp);
}
temp = ""; // 清空 temp
}
return;
}
// 单层逻辑
for(int i = startIndex; i < s.size(); i++){
string s_temp = s.substr(startIndex, i - startIndex + 1);
if(isValue(s_temp)){
path.push_back(s_temp);
pointnum += 1;
backtracking(s, i + 1, pointnum);
pointnum -= 1;
path.pop_back();
}else{
break;
}
}
}
vector<string> restoreIpAddresses(string s) {
backtracking(s, 0, 0);
if (s.size() < 4 || s.size() > 12) return result; // 算是剪枝了
return result;
}
};
方法二:重新开了一个path数组,用于存放已经切割好的字符串,然后都校验通过的话,一起放到result中
还有个卡哥代码的变形,不是直接在字符串上添加逗点:
class Solution {
public:
vector<string> result;
vector<string> path; // 判断切割后的单个字符是否合法
string temp;
bool isValue(string s, int start, int end){
if (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;
}
void backtracking(string s, int startIndex, int pointnum){
// 终止条件
if(pointnum == 3){
// 分成4段,需要检查最后一段是否合法
if(isValue(s, startIndex, s.size()-1)){
for(int i = 0; i < path.size(); i++){
temp += path[i] + '.';
}
temp += s.substr(startIndex, s.size() - 1 - startIndex + 1);
result.push_back(temp);
temp = ""; // 清空 temp
}
return;
}
// 单层逻辑
for(int i = startIndex; i < s.size(); i++){
if(isValue(s, startIndex, i)){
string s_temp = s.substr(startIndex, i - startIndex + 1);
path.push_back(s_temp);
pointnum += 1;
backtracking(s, i + 1, pointnum);
pointnum -= 1;
path.pop_back();
}else{
break;
}
}
}
vector<string> restoreIpAddresses(string s) {
backtracking(s, 0, 0);
if (s.size() < 4 || s.size() > 12) return result; // 算是剪枝了
return result;
}
};
题目二:78.子集
Leetcode题目:【78.子集】
参考:【代码随想录之子集问题】
组合和切割都是在终止条件处收获结果,但是子集问题是在每个节点处收集结果(每次递归都要放到结果集中)
按照回溯模板:
(1)终止条件:startIndex 取到了nums.size(),这个时候就到了叶子节点了。
if(startIndex >= nums.size() return;
(2)收获结果是在每个节点那里:
总体代码如下:
class Solution {
public:
vector<int> path;
vector<vector<int>> result;
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;
}
};
题目三:90.子集II
Leetcode题目:【90.子集II】
参考:【代码随想录之子集II】
数组中有重复元素的,就要想到树层去重和树枝去重,需要的是树层去重,所以采用used来实现。
class Solution {
public:
vector<int> path;
vector<vector<int>> result;
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;
}else{
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) {
vector<bool> used(nums.size(), false);
sort(nums.begin(), nums.end()); // 先排序
backtracking(nums, 0, used);
return result;
}
};