目录
一、做题心得
二、题目与题解
题目一:93.复原IP地址
题目链接
题解:回溯--分割问题
题目二:78.子集
题目链接
题解:回溯--子集问题
题目三:90.子集II
题目链接
题解:回溯--子集问题
三、小结
一、做题心得
今天的题个人感觉第一道 93. 复原 IP 地址 - 力扣(LeetCode)还是挺有难度的,虽然跟昨天打卡的分割回文串很相似,但是自己做的时候还是有点吃力。后边两道题就很简单了,感觉和前两天练的组合问题差不多,个人感觉问题不大。
这里直接开始今天的题目吧。
二、题目与题解
题目一: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
中的任何数字。你可以按 任何 顺序返回答案。示例 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"]提示:
1 <= s.length <= 20
s
仅由数字组成
题解:回溯--分割问题
这个题也是很明显的分割问题。 、
分析题意:有效IP地址分为4个部分,每一部分由.隔开,每一部分都有限制要求(这个限制要求往往会出现if从句,如果复杂的话需要自定义函数)
关键:
1.自定义函数判断IP地址某部分是否有效(即是否符合题目要求)
2.如何截取字符串s的各种子串:substr()函数
3.如何实现剪枝(具体看代码):三处--两处是特殊情况的单独处理,一处是循环遍历对终止条件的缩小
4.终止条件是什么:递归遍历完整个字符串的同时,IP地址恰好被分为4个部分
想清楚上边四点,这道题也就好解决了,剩下的就跟昨天 131. 分割回文串 一个道理。
代码如下:
class Solution {
public: vector<string> ans; vector<string> vec;bool isRange(string substring) { //判断字符串是否为有效IP地址的一部分if (substring.size() != 1 && substring[0] == '0') { //前导0无效(如012,023等等)return false; } int num = stoi(substring); //将字符串转化为数字(字符串只包含数字,不考虑负数)return num <= 255; } void backtrack(string& s, int start) { if (vec.size() == 4 && start == s.size()) { //终止条件:当vec中存储了4个部分,并且刚好遍历完了整个字符串s时,说明找到了一个有效的IP地址string ip = ""; for (int i = 0; i < vec.size(); ++i) { //vec每一部分结束后添加.(最终结果存放在ip里)ip += vec[i]; if (i < vec.size() - 1) { ip += "."; } } ans.push_back(ip); return; } if (start == s.size() && vec.size() != 4) { //剪枝1:当遍历完了整个s但IP地址部分数不为4时,重新返回递归return;}for (int i = start; i < start + 3 && i < s.size(); i++) { //剪枝2:i < start + 3表示IP地址每一部分不超过三位数string substring = s.substr(start, i - start + 1); //截取字符串start到i的子串if (!isRange(substring)) { continue;} vec.push_back(substring); backtrack(s, i + 1); vec.pop_back(); } } vector<string> restoreIpAddresses(string s) { if (s.size() < 4 || s.size() > 12) { //剪枝3:不可能存在有效IP地址情况直接返回空向量return ans;}backtrack(s, 0); return ans; }
};
题目二:78.子集
题目链接
78. 子集 - 力扣(LeetCode)
给你一个整数数组
nums
,数组中的元素 互不相同 。返回该数组所有可能的子集
(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3] 输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]示例 2:
输入:nums = [0] 输出:[[],[0]]提示:
1 <= nums.length <= 10
-10 <= nums[i] <= 10
nums
中的所有元素 互不相同
题解:回溯--子集问题
子集问题个人感觉跟组合问题差不多,可能最大的不同就在于终止条件那里。
这里我们需要注意了,对于对于子集问题:当我们要得到一个数组集合的全部子集,其实是没有终止条件的(当然,你也可以想成有,就是全部都自然遍历添加完,不过这其实跟没有也没区别),也就是说,我们只需要把每一个递归得到的数组添加到结果里边去即可。这里就要求我们对模板的有效运用了,不要没有终止条件就硬想半天。
代码如下:
class Solution {
public:vector<vector<int>> ans;vector<int> vec;void backtrack(vector<int>& nums, int start) {ans.push_back(vec); //注意:所有子集都要添加,没有终止条件限制 for (int i = start; i < nums.size(); i++) {vec.push_back(nums[i]);backtrack(nums, i + 1);vec.pop_back();} }vector<vector<int>> subsets(vector<int>& nums) {backtrack(nums, 0);return ans;}
};
题目三:90.子集II
题目链接
给你一个整数数组
nums
,其中可能包含重复元素,请你返回该数组所有可能的子集
(幂集)。解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
示例 1:
输入:nums = [1,2,2] 输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]示例 2:
输入:nums = [0] 输出:[[],[0]]提示:
1 <= nums.length <= 10
-10 <= nums[i] <= 10
题解:回溯--子集问题
这个题算是上面那道的进化版吧,不过思路也差不多,只是数组里出现了重复元素,而要求结果中不能出现重复子集--这不就和昨天打卡不能出现重复组合一样了:对数组排序 + 跳过数组相邻相同的元素(横向同层处理使结果不出现重复子集)。
不理解的话,可以看看昨天的打卡内容,这里就不做分析了。
代码如下:
class Solution {
public:vector<vector<int>> ans;vector<int> vec;void backtrack(vector<int>& nums, int start) {ans.push_back(vec);for (int i = start; i < nums.size(); i++) {if (i > start && nums[i] == nums[i - 1]) { //横向去重:用以跳过同一树层使用过的(重复)元素continue; //注意这里是continue而不是break(break会直接跳出循环,这样一旦出现重复元素,会完全停止遍历剩余的元素,这会导致生成的子集不完整)}vec.push_back(nums[i]);backtrack(nums, i + 1);vec.pop_back();}}vector<vector<int>> subsetsWithDup(vector<int>& nums) {sort(nums.begin(), nums.end()); //先排序,这样重复的元素就会相邻backtrack(nums, 0);return ans;}
};
三、小结
今天的打卡就到此结束了,后边也会继续加油。最后,我是算法小白,但也希望终有所获。