Day24 回溯算法:93.复原IP地址 78.子集 90.子集Ⅱ

69 阅读5分钟

93.复原IP地址

题目链接:93.复原IP地址

难度指数:😀😐😕😫

本期本来是很有难度的,不过 大家做完 分割回文串 之后,本题就容易很多了。

本题不仅要对输入的字符串进行切割,还要对切割的段进行一个合法性的判断。

24.01.png

即使画出了树形结构,在写代码的时候还会有疑惑:切割线如何模拟?切割出来的子串如何表达?

代码思路:

一维数组result,存放分割出来的合法字符串

startIndex,在进入下一层递归的时候,让我们知道是从剩下的字符串里面进行切割。(就是从哪里进行切割)

IP里面 . 的数量决定了树的深度,有3个 . 了就不需要再往下切割(多加 . 就不是合法IP了)。

每次加了 *. *都是对前面字符串进行了合法性的判断;我们还需要判断最后一段是否合法,只有合法了才能加入结果集了。

在代码中,startIndex表示分割线

 vector<string> result;
 void backtracking(const stirng& s, int startIndex, int pointSum) {
     //终止条件
     if (pointSum == 3) {
         if (isValid(s, startIndex, s.size() - 1)) {  //isValid()判断子串是否合法 (数字不能有前导0,区间不能超过255,子串不能有非法字符)
             result.push_back(s);  //在切割的地方会加上 .  然后直接把s放到结果集
             return;
         }
     }
     //单层搜索的逻辑
     for (int i = startIndex; i < s.size(); i++) {
         //切割之后,对子串进行合法性的判断
         if (isValid(s, startIndex, i)) {  //  [startIndex, i]
             s.insert(s.begin() + i + 1, '.');
             pointSum += 1;
             backtracking(s, i + 2, pointSum);  //递归
             s.erase(s.begin() + i + 1);
             pointSum -= 1;
         }
     }
 }

本题关键在于:如何模拟切割的过程。 其实和我们去做组合问题,去取数的时候是一样的,

另一个难点:要对子串[startIndex, i]进行合法性的判断,

24.01.png

AC代码: (核心代码模式)

 class Solution {
 private:
     vector<string> result;  //存放分割出来的合法字符串
     void backtracking(string& s, int startIndex, int pointSum) {
         //终止条件
         if (pointSum == 3) {
             if (isValid(s, startIndex, s.size() - 1)) {  //isValid()判断子串是否合法
                 result.push_back(s);  //在切割的地方会加上 . 然后直接把s放进结果集  
             }
             return;
         }
         //单层搜索的逻辑
         for (int i = startIndex; i < s.size(); i++) {
             //切割之后,对子串进行合法性的判断
             if (isValid(s, startIndex, i)) {  // [startIndex, i]
                 s.insert(s.begin() + i + 1, '.');
                 pointSum += 1;
                 backtracking(s, i + 2, pointSum);  //递归
                 pointSum -= 1;  //回溯
                 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开头的数字(前导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<string> restoreIpAddresses(string s) {
         result.clear();
         if (s.size() < 4 || s.size() > 12) {
             return result;  //算是剪枝了
         }
         backtracking(s, 0, 0);
         return result;
     }
 };

题目还是比较难的!

78.子集

题目链接:78.子集

难度指数:😀😕🙁

子集问题,就是收集树形结构中,每一个节点的结果。 整体代码其实和 回溯模板都是差不多的。

树形结构

24.02.png

我们想收获的结果都在(除根节点的)每一个节点里面,都要放进结果集里面。

前面的题目都是在终止条件收获结果,

子集的题目就不是在终止条件收获结果了,而是每进入一层递归,都要把本层递归的单个结果放进结果集。

代码思路:

 vector<int> path;
 vector<vector<int>> result;
 void backtracking(nums, 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();  //回溯
     }
     return;
 }

思考:为什么收获结果在终止条件的上面? (为什么不写在终止条件下面?)

答:获取最后一个元素的时候,这个结果才不会被省略掉。

13:00

本题终止条件可以不写初学者还是建议写,养成习惯),因为当startIndex >= nums.size(),这个for循环也不会执行,

下面照常会终止,本层递归也会结束。

AC代码: (核心代码模式)

 class Solution {
 private:
     vector<int> path;
     vector<vector<int>> result;
     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();  //回溯
         }
         return;
     }
 public:
     vector<vector<int>> subsets(vector<int>& nums) {
         result.clear();
         path.clear();
         backtracking(nums, 0);
         return result;
     }
 };

90.子集Ⅱ

题目链接:90.子集Ⅱ

难度指数:😀😕🙁

大家之前做了 40.组合总和II 和 78.子集 ,本题就是这两道题目的结合,建议自己独立做一做,本题涉及的知识,之前都讲过,没有新内容。

题目给我们的集合允许有重复元素,

24.03.png

树层上,相邻两个元素,如果数值一样的话,它们所得到的子集就是重复的子集,需要去重

每一个节点都是我们要收集的结果,(意味着子集问题在收获结果集的时候,代码位置跟组合的题目不一样)。

本题就是在子集的基础上多了2行去重的代码。

代码思路:

需要先将集合进行排序

 vector<int> path;
 vector<vector<int>> result;
 void backtracking(nums, startIndex, used) {
     result.push_back(path);  //进入递归之后,就将单个节点的结果放到结果集
     for (int i = startIndex; i < nums.size(); i++) {
         //树层剪枝的过程放在for循环里的第一步
         if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == 0) {  //⚠️需要对树层和树枝做区别
             continue;
         }
         path.push_back(nums[i]);
         used[i] = true;
         backtracking(nums, i + 1, used);  //递归
         path.pop_back();  //回溯
         used[i] = false;
     }
 }

需要对树层和树枝进行区分,不然会误杀!

在做回溯算法相关题目的时候,集合中出现了重复元素,如何去重?

基本上都是写上面的去重逻辑

AC代码: (核心代码模式)

 class Solution {
 private:
     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++) {
             //树层剪枝的过程放在for循环里面的第一步
             if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == 0) {
                 continue;
             }
             path.push_back(nums[i]);
             used[i] = true;
             backtracking(nums, i + 1, used);  //递归
             path.pop_back();
             used[i] = false;
         }
     }
 public:
     vector<vector<int>> subsetsWithDup(vector<int>& nums) {
         result.clear();
         path.clear();
         vector<bool> used(nums.size(), false);
         sort(nums.begin(), nums.end());  //去重之前先排序
         backtracking(nums, 0, used);
         return result;
     }
 };