目录
一、93. 复原 IP 地址
1.题目描述
有效 IP 地址 正好由四个整数(每个整数位于 0
到 255
之间组成,且不能含有前导 0
),整数之间用 '.'
分隔。
- 例如:
"0.1.2.201"
和"192.168.1.1"
是 有效 IP 地址,但是"0.011.255.245"
、"192.168.1.312"
和"192.168@1.1"
是 无效 IP 地址。
给定一个只包含数字的字符串 s
,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s
中插入 '.'
来形成。你 不能 重新排序或删除 s
中的任何数字。你可以按 任何 顺序返回答案。
示例 1:
输入:s = "25525511135" 输出:["255.255.11.135","255.255.111.35"]
示例 2:
输入:s = "0000" 输出:["0.0.0.0"]
示例 3:
输入:s = "101023" 输出:["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"]
2.解题思路
通过像字符串添加小数点的方式,来分割字符串。pointNum记录小数点数量。
关键点1:如何确定终止条件?
- 当pointNum为3的时候,代表字符串已经被分成了四段。此时对第四段的子串做有效判断
- 注意点:当第三个小数点在字符串末尾的时候,这种情况是无效的,因此在isValid()函数中,需要对begin和end做判断。
关键点2:如何确定单层逻辑?
- 对切割的子串做有效判断,如果有效,就在后面添加‘.’来进行分割。
- 注意点:因为添加了‘.’,所以在深层递归时候,startIndex就不是 i + 1,而是 i + 2了。
3.代码实现
class Solution {
public:
vector<string> result;//结果集
//确定函数参数
void backtracking(string& s,int startIndex,int pointNum){
//pointNum用来记录小数点数量,一旦达到3,说明s被分成四段,此时验证第四段是否合法即可
//确定终止条件
if(pointNum == 3){
//说明已经被分成了四段,只需要验证第四段是否合法即可,如果合法就加入到结果集中
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)){
//合法的话就分隔一下,在i后面加逗号
s.insert(s.begin() + i + 1,'.');
pointNum++;
//深度递归
backtracking(s,i + 2,pointNum);//注意这里插入了'.' 所以下一个startIndex要 + 2
//回溯操作
s.erase(s.begin() + i + 1);
pointNum--;
}
else//如果不合法,结束本层的逻辑
break;
}
}
bool isValid(const string& s,int begin,int end){
//这个if语句是对与终止条件判断的补充(第三个小数点写在了字符串的末尾的情况,此时startIndex 是大于 s.size() - 1)
if(begin > end)
return false;
//不能有前导0但是不能排除只有0的情况
if(s[begin] == '0' && begin != end)
return false;
//每段子串大小应该位于0-255
int num = 0;
for(int i = begin;i <= end;i++){
num = num * 10 + s[i] - '0';
if(num > 255)
return false;
//不能有数字以外的字符
if(s[i] < '0' || s[i] >'9')
return false;
}
//都检查完毕后,返回true
return true;
}
vector<string> restoreIpAddresses(string s) {
backtracking(s,0,0);
return result;
}
};
二、78. 子集
1.题目描述
给你一个整数数组 nums
,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3] 输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0] 输出:[[],[0]]
2.解题思路
子集问题,也是组合问题。只不过在遍历树的时候,需要把每个节点对应的路径path数组都收集起来。
ps:这里的终止条件可以不写。
3.代码实现
class Solution {
public:
vector<int> path;//收集路径上的元素
vector<vector<int>> result;//结果集
void backtracking(const vector<int>& nums,int startIndex){
//每个节点的路径都要收集
result.push_back(path);
//确定终止条件
if(startIndex >= nums.size()){//可以不加,因为在for循环中也会结束
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
1.题目描述
给你一个整数数组 nums
,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
示例 1:
输入:nums = [1,2,2] 输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]
示例 2:
输入:nums = [0] 输出:[[],[0]]
2.解题思路
注意本题所给数组nums中的元素可能是重复的,而子集问题(也就是组合问题)不在意元素的下标顺序,因此我们要做去重操作。
关键点:如何去重?
- 先对数组做排序,让相同元素处于邻近位置。如果这个元素和前一个元素一样,直接continue。
3.代码实现
class Solution {
public:
vector<int> path;
vector<vector<int>> result;//结果集
//1.确定函数参数
void backtracking(const vector<int>& nums,int startIndex){
//每个节点都要收集路径
result.push_back(path);
//2.确定终止条件(本题可以不写)
//3.确定单层逻辑
for(int i = startIndex;i < nums.size();i++){
//去重
if(i > startIndex && nums[i] == nums[i - 1])
continue;
path.push_back(nums[i]);
backtracking(nums,i + 1);
path.pop_back();//回溯操作
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(),nums.end());//先排序
backtracking(nums,0);
return result;
}
};