组合问题:集合内元素的组合,不同集合内元素的组合
分割问题:本质还是组合问题,注意一下如何分割字符串
回溯模板伪代码
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
93.复原IP地址
有效 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 中的任何数字。你可以按 任何 顺序返回答案。
关键点
判断地址是否有效
集合内同一元素不可重复选取
思路
仍然是一个集合内元素的组合问题
backtracking(s, i + 2);是i+2,而非i+1,首先是不能重复所以应该i+1,但实际操作插入了’.',所以应该是i+2
- 横向遍历是针对当前字符集合,遍历该集合的所有字符作为截取点
- 纵向遍历是针对截取后得到的子字符集合(不可重复选取,故下一层子集不包含上层取的元素:上层截取点其右边所有元素)
代码
class Solution {
public:
vector<string> result;
int partNum = 0;
bool IsValid(const string &s, int begin, int end) {
if(end - begin > 2 || end < begin) return false;
int num = 0;
for(int i = begin; i <=end; i++) {
if(s[begin] == '0' && begin != end) return false; //不能含有前导 0 以及最后第四个整数不为空
if(s[i] < '0' || s[i] > '9') return false; // 必须数字
num = num * 10 + (s[i] - '0');
if(num > 255) return false; //整数不能超过255
}
return true;
}
void backtracking(string &s, int startIndex) {
if(partNum == 3) { // 已插入3个'.',若第四个整数有效则添加到结果集
if(IsValid(s, startIndex, s.size()-1)) {
result.push_back(s);
}
return;
}
int flag = false;
for(int i = startIndex; i < s.size(); i++) {
if(IsValid(s, startIndex, i)) {
s.insert(s.begin() + i + 1 , '.'); //合法则插入'.'
partNum++;
backtracking(s, i + 2);
s.erase(s.begin() + i + 1); //回溯
partNum--;
} else break; // 不合法,直接结束本层循环,因为本层后面的也必不可能合法了
}
}
vector<string> restoreIpAddresses(string s) {
result.clear();
if(s.size() < 4 || s.size() > 12) return result; //剪枝
backtracking(s, 0);
return result;
}
};
78.子集
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
关键点
集合内同一元素不能被重复选取
子集问题是找树的所有节点
思路
组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点!子集也是一种组合问题
- 横向遍历是针对当前集合,遍历取当前集合的所有元素
- 纵向遍历是针对取完元素得到的子集合,遍历下一个子集合 (不可重复选取,故下一层子集不包含上层取的元素:上层所取元素其右所有元素)
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int startIndex) {
if(startIndex > nums.size()) return;
for(int i = startIndex; i < nums.size(); i++) {
path.push_back(nums[i]); //把遍历的所有节点都添加
result.push_back(path);
backtracking(nums, i+1); //不可重复选取,故下一层子集不包含上层取的元素:上层所取元素其右所有元素
path.pop_back();
}
}
vector<vector<int>> subsets(vector<int>& nums) {
result.clear();
result.push_back(path);
backtracking(nums, 0);
return result;
}
};
90.子集II
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
关键点
集合有重复元素
树层去重
集合内同一元素不能被重复选取
思路
元素在同一个组合内是可以重复的,怎么重复都没事,但两个组合不能相同——>去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重。
本质是一个组合问题,集合内同一元素不能被重复选取
- 横向遍历是针对当前集合,遍历该字符集的所有元素(树层去重,将同层已取过的相同值的元素跳过)
- 纵向遍历是针对取后得到的子集(集合内同一元素不能被重复选取)
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
public:
void backtracking(vector<int>& nums, int startIndex) {
for(int i = startIndex; i < nums.size(); i++) {
if(i != startIndex && nums[i] == nums[i-1]) continue; // 不同解集间不能重复,在树层去重
path.push_back(nums[i]);
result.push_back(path); //把遍历的所有节点都添加
backtracking(nums, i+1);
path.pop_back();
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
result.clear();
path.clear();
result.push_back(path);
sort(nums.begin(), nums.end());
backtracking(nums, 0);
return result;
}
};