93.复原IP地址
93. 复原 IP 地址 - 力扣(LeetCode)
有效 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
中的任何数字。你可以按 任何 顺序返回答案。
思路:切割问题,切割问题就可以使用回溯搜索法把所有可能性搜出来,和刚做过的131.分割回文串 就十分类似了。
切割问题可以抽象为树型结构,如图:
递归参数:startIndex一定是需要的,因为不能重复分割,记录下一层递归分割的起始位置。
本题我们还需要一个变量pointNum,记录添加逗点的数量。
终止条件:本题明确要求只会分成4段,所以不能用切割线切到最后作为终止条件,而是分割的段数作为终止条件。
pointNum表示逗点数量,pointNum为3说明字符串分成了4段了。
然后验证一下第四段是否合法,如果合法就加入到结果集里
单层搜索的逻辑:在
for (int i = startIndex; i < s.size(); i++)
循环中 [startIndex, i] 这个区间就是截取的子串,需要判断这个子串是否合法。如果合法就在字符串后面加上符号
.
表示已经分割。如果不合法就结束本层循环
class Solution {
public:
vector<string>result;
void backtracking(string& s, int startIndex, int pointNum ){
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)){
s.insert(s.begin()+i+1, '.');//在i的后面插入一个逗点
pointNum++;
backtracking(s, i+2, pointNum);//递归
// 回溯
pointNum--;
s.erase(s.begin()+i+1);
}else break;
}
}
bool isValid(const string& s, int start, int end){
if(start>end)return false;
if(s[start]=='0'&& start!=end) return false;//数字以0开头时不合法
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) return false;
}
return true;
}
vector<string> restoreIpAddresses(string s) {
result.clear();
if(s.size()<4||s.size()>12)return result;
backtracking(s,0,0);
return result;
}
};
78.子集
力扣题目链接(opens new window)
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例: 输入: nums = [1,2,3] 输出: [ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], [] ]
思路: 如果把 子集问题、组合问题、分割问题都抽象为一棵树的话,那么组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点!
其实子集也是一种组合问题,因为它的集合是无序的,子集{1,2} 和 子集{2,1}是一样的。
那么既然是无序,取过的元素不会重复取,写回溯算法的时候,for就要从startIndex开始,而不是从0开始!
以示例中nums = [1,2,3]为例把求子集抽象为树型结构,如下:
从图中红线部分,可以看出遍历这个树的时候,把所有节点都记录下来,就是要求的子集集合
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) {
path.clear();
result.clear();
backtracking(nums,0);
return result;
}
};
90.子集II
力扣题目链接(opens new window)
给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
- 输入: [1,2,2]
- 输出: [ [2], [1], [1,2,2], [2,2], [1,2], [] ]
思路:和前一题类似,多了去重操作,注意数组排序后,同一层不重复选,同一枝可重复选;
用示例中的[1, 2, 2] 来举例,如图所示: (注意去重需要先对集合排序)
从图中可以看出,同一树层上重复取2 就要过滤掉,同一树枝上就可以重复取2,因为同一树枝上元素的集合才是唯一子集!
同一层去重指:num[i]=nums[i-1]&&used[i-1]==false;
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]==false){
continue;//跳过本层重复元素
}
used[i]=true;
path.push_back(nums[i]);
backtracking(nums,i+1, used);//纵向遍历
path.pop_back();//回溯
used[i]=false;
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
path.clear();
result.clear();
vector<bool> used(nums.size(), false);
sort(nums.begin(), nums.end());
backtracking(nums,0, used);
return result;
}
};
参考:代码随想录