题目链接:93 复原IP地址
题干:有效 IP 地址 正好由四个整数(每个整数位于
0
到255
之间组成,且不能含有前导0
),整数之间用'.'
分隔。
- 例如:
"0.1.2.201"
和"192.168.1.1"
是 有效 IP 地址,但是"0.011.255.245"
、"192.168.1.312"
和"[email protected]"
是 无效 IP 地址。给定一个只包含数字的字符串
s
,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在s
中插入'.'
来形成。你 不能 重新排序或删除s
中的任何数字。你可以按 任何 顺序返回答案。
思考一:回溯法。先定义结果集result,再考虑回溯函数:
参数 | 含义 |
s | 题干给定字符串 |
startIndex | 下一层循环搜索的起始位置 |
path | 保存的部分IP |
count | 分隔符的个数 |
终止条件:count个数满四个说明path已经可以组成IP,再判断startIndex是否大于字符串长度,若是则将path中最后一个分隔符去除后加入结果集result,否则说明字符串中数字未全用到返回。
单层搜索逻辑:从startIndex开始循环取出字符串里的数并判断组成的子IP是否满足条件,若满足则递归处理,处理后再回溯。(特殊考虑:若此次搜索首个数字为0则该循环只处理一次)
代码:
class Solution {
public:
vector result;
void backtracking(const string& s, int startIndex, string path, int count) {
if (count == 4) { //分隔符.的个数满足条件
if (startIndex >= s.size()) { //字符串内数字全用到
path.resize(path.size() - 1); //去除最后面的分隔符
result.push_back(path);
}
return;
}
int num = 0;
for (int i = startIndex; i < s.size(); i++) {
num = num * 10 + (s[i] - '0');
if (i > startIndex && s[startIndex] == '0') break; //首个数字为0只处理一次
if (num > 255) break; //已经超过限定值
count++;
backtracking(s, i + 1, path + to_string(num) + ".", count);
count--;
}
}
vector restoreIpAddresses(string s) {
result.clear();
string path = "";
backtracking(s, 0, path, 0);
return result;
}
};
思考二:与思考一的区别:直接在s中修改并把判断子IP是否合理单独设计函数处理。
代码:
class Solution {
private:
vector result;// 记录结果
// startIndex: 搜索的起始位置,pointNum:添加逗点的数量
void backtracking(string& s, int startIndex, int pointNum) {
if (pointNum == 3) { // 逗点数量为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的后面插入一个逗点
pointNum++;
backtracking(s, i + 2, pointNum); // 插入逗点之后下一个子串的起始位置为i+2
pointNum--; // 回溯
s.erase(s.begin() + i + 1); // 回溯删掉逗点
} else break; // 不合法,直接结束本层循环
}
}
// 判断字符串s在左闭又闭区间[start, end]所组成的数字是否合法
bool isValid(const 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;
}
public:
vector restoreIpAddresses(string s) {
result.clear();
if (s.size() < 4 || s.size() > 12) return result; // 算是剪枝了
backtracking(s, 0, 0);
return result;
}
};
题目链接:78 子集
题干:给你一个整数数组
nums
,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
由上图可以看出,收集每个搜索的节点子集即可 。此题也不需要终止条件。
代码:
class Solution {
public:
vector> result;
vector node;
void backtracking(vector& nums, int startIndex) {
result.push_back(node); //收集子集,包括空集
for (int i = startIndex; i < nums.size(); i++) {
node.push_back(nums[i]);
backtracking(nums, i + 1);
node.pop_back();
}
}
vector> subsets(vector& nums) {
result.clear();
node.clear();
backtracking(nums, 0);
return result;
}
};
题目链接:90 子集II
题干:给你一个整数数组
nums
,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
思考:与上题的区别:数组中的数字可重复。因此同层搜索逻辑中重复数字只处理一次。所以先将数组的元素进行排序,其次在循环中除首个(startIndex)处理的数字外其余数字均判断是否与前一位数字相同,若相同则说明同一数字重复出现跳过后序处理进行遍历下一个数字。
代码:
class Solution {
public:
vector> result;
vector node;
void backtracking(vector& nums, int startIndex) {
result.push_back(node); //收集子集,包括空集
for (int i = startIndex; i < nums.size(); i++) {
if (i > startIndex && nums[i] == nums[i - 1]) //同层搜索中重复的数字只处理一次
continue;
node.push_back(nums[i]);
backtracking(nums, i + 1);
node.pop_back();
}
}
vector> subsetsWithDup(vector& nums) {
result.clear();
node.clear();
sort(nums.begin(),nums.end());
backtracking(nums, 0);
return result;
}
};
自我总结:
分清子集问题和组合问题、分割问题的的区别:
子集是收集树形结构中树的所有节点的结果。
组合问题、分割问题是收集树形结构中叶子节点的结果。