目录
93. 复原 IP 地址
分析:
- 每个整数位于 0 到 255 之间组成,且不能含有前导 0
- 以上面为依据,进行判断是否合法,分割是遍历的,每一种情况都会出现
- 判断结束后插入 小圆点
所以就是分割 + 判断 + 插入
(我回看一下我写的思路,思路仿佛大体方向都是正确的,但是不清晰不够直白,还是混沌的)
分割是使用回溯法。
- 终止条件:分割的段数作为终止条件
- 单层逻辑:
在
for (int i = startIndex; i < s.size(); i++)
循环中 [startIndex, i] 这个区间就是截取的子串,需要判断这个子串是否合法。如果合法就在字符串后面加上符号.
表示已经分割。如果不合法就结束本层循环: -
判断子串是否合法; 字符串转数字
78. 子集
分析:
- 判断是否是子集,
- 原集合排序,原集合的元素不重复,还有必要排序吗,不需要
- 去重:需要使用startIndex
- 什么时候取子集:在每一个树层处取值
代码:
class Solution {
private:
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); //最终结果的填入放入for循环中,带来的区别,就是没有空子集
backtracking(nums, i+1);
path.pop_back();
}
}
public:
vector<vector<int>> subsets(vector<int>& nums) {
result.clear();
path.clear();
backtracking(nums,0);
return result;
}
};
result.push_back(path); //最终结果的填入放入for循环中,带来的区别,就是没有空子集
代码随想录:
// 收集子集,要放在终止添加的上面,否则会漏掉自己
void backtracking(vector<int>& nums, int startIndex) {
result.push_back(path); // 收集子集,要放在终止添加的上面,否则会漏掉自己
if (startIndex >= nums.size()) { // 终止条件可以不加
疑问:
那这样不同两个位置带来的改变逻辑上究竟是因为什么?
第一次进入递归的时候,可以获取空子集
复杂度分析:
- 时间复杂度:O( n x 2^n) ,一共 2^n 个状态,每种状态需要 O(n)的时间来构造子集
- 空间复杂度:O(n),临时数组的空间代价是O(n),递归室栈空间的代价为 O(n)
- 问题:为什么是 2^n 个状态 ???
对原集合中的每一个元素,都有选中和不选两种可能;含有n个元素的集合的任一子集都可以看作是分别对每一个元素选择后的最终结果,共进行了n次选择;
所以,它的子集的个数是n个2连乘,即2^n个。
【好比是:n个不同的小球,一次拿出若干个小球(可以不拿),共有多少种方法】
总结:
- 组合问题:
- 分割问题:
但是要清楚子集问题和组合问题、分割问题的的区别,
子集是收集树形结构中树的所有节点的结果。
而组合问题、分割问题是收集树形结构中叶子节点的结果。
90. 子集 II
分析
-
原集合中包含重复元素,在树层处取子集结果,此处就要去重
-
去重方式:原集合排序,重复元素挨在一起(感觉没有必要,如果在for循环进行条件设置);进入for循环的判定条件——使用use数组
同一树层上重复取2 就要过滤掉,同一树枝上就可以重复取2,因为同一树枝上元素的集合才是唯一子集!
本题就是其实就是回溯算法:求子集问题! (opens new window)的基础上加上了去重,去重我们在回溯算法:求组合总和(三) (opens new window)也讲过了
三个去重的方法:
used数组:
continue 的用法:跳出本次当前的for循环,进入到下一次的for循环;
使用used数组一定要进行排序,因为进行的是前后下标的比较
当重复的元素数量超过2次时,如何判断重复:used数组的值为未被使用,nums数组前后标进行比较
used数组定义的位置并不是类中的属性,而是进行函数使用时创建的
result数组填值,设定在函数的一开始就是表明了,在向path中填值室有限制的,并不会将不符合的结果填入。
如果要是全排列的话,每次要从0开始遍历,为了跳过已入栈的元素,需要使用used。
set去重:
unordered_set 的创建位置设定在 backtracking回溯函数中,那不就是表明每次调用回溯函数就会创建一个哈希表
从宏观角度来看,每一个树层,每一个for循环同用一个for循环,进行树层去重
不使用used数组而是startIndex:
if (i > startIndex && nums[i] == nums[i - 1] ) {
continue;
}
复杂度分析:
- 时间复杂度: O(n x 2^n),其中n是数组nums 的长度,sort排序的时间复度为O(n logn)。 最坏的情况下nums 无重复元素,需要枚举其中 2^n 个子集,每个子集加入答案需要拷贝一份,耗时 O(n) 一共需要 O(n x 2^n)+O(n)=O(n x 2^n) 的时间来构造子集。由于在渐进意义上 O(nlogn) 小于 O(nx 2^n),故总的时间复杂度为 O(n x 2^n)
-
空间复杂度:O(n)。临时数组 t 的空间代价是 O(n),递归时栈空间的代价为 O(n)。
每个子集加入答案需要拷贝一份,耗时 O(n) ?????