代码随想录第二十四天 | Leecode 93. 复原IP地址 、78. 子集、 90.子集II
Leecode 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 中的任何数字。你可以按 任何 顺序返回答案。
- 示例 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"]
回溯法,解题思路与代码
感觉这道题对我来说确实有不小难度,思考了并尝试了很久都没有什么思路,最后是看完了卡哥的讲解才想出来该怎么做的。首先是需要明确本题目标,要在给定的字符串中插入字符'.',同时还需要将所有满足条件的字符串都记录在一个vector中输出。那么首先是对于字符串中插入与删除字符的操作:
str.insert(str.begin() + pos, 'char'),在字符串中插入字符使用insert()函数str.erase(pos , length),在字符串中删除指定长度字符,使用erase()函数
随后进一步讨论回溯的思路:
- 由于本题要求给定合法IP的条件比较繁杂,所以一个自然的想法是使用一个返回布尔类型的函数来判断当前字符串是否合法。而此时又需要明确该函数的输入,如果考虑输入的是整个字符串,那么可能会遇到当前字符串前一半加了'.',而后一半没加'.'的情况,也不能说每次只在已经将三个'.'都插入之后我们再去判断当前字符串是否合法,否则会导致消耗很多时间在构建不合法的IP字符串的过程中。为此我们需要一个能够实时判断当前分割字符串并插入一个'.'的操作是否合法的函数,从而能够在一旦发现本次分割不合法后迅速剪枝。那么我们理想的情况是输入字符串的一个区间,即本次分割后所产生的长度不大于3的数字字符是否小于255且不能以0开头。这里输入的参数采用:
(const string& s, int left, int right)的方式,表示本次判断字符串s中的左闭右闭区间[left, right]是否合法。 - 在能够判断本次分割是否合法之后,就可以按照回溯的框架来写代码了:
- 如果当前已经分割完成(已经插入过3个点,故需要一个变量
pointNum来记录已经插入过几个点),并且最后一次分割合法,那么将整个字符串存入结果中 - (如果还未分割完成)则在for循环中用i遍历分割长度,此时需要一个变量
start来记录开始分割的位置- 在循环中每一次判断不合法则直接返回,如果合法则继续插入、递归、回溯
- 如果当前已经分割完成(已经插入过3个点,故需要一个变量
那么根据上面的思路不难写出下面代码:
class Solution {
public:
vector<string> result;
void backTracking(string& s, int start, int pointNum){
if(pointNum == 3 && isValid(s, start, s.size()-1)){
result.push_back(s);
return;
}
for(int j = start; j < s.size(); j++){
if(j - start > 2) break; // 剪枝,如果长度大于3,则直接跳出循环
if(!isValid(s, start, j)) return;
s.insert(s.begin()+j +1, '.'); // 插入一个.
backTracking(s, j+2, pointNum+1); // 递归
s.erase(j+1, 1); // 回溯,删掉插入的.
}
}
bool isValid(const string& s, int left, int right){
if(left > right) return false; // 输入的left要小于等于right
if(right - left > 2) return false; // 检查长度不能超过3
if(s[left] == '0' && right != left) return false; // 检查不能有前导0
if(right - left == 2){ // 检查数值要小于等于255
if(s[left] > '2') return false;
if(s[left] == '2' && s[left+1] > '5') return false;
if(s[left] == '2' && s[left+1] == '5' && s[left+2] > '5') return false;
}
return true;
}
vector<string> restoreIpAddresses(string s) {
backTracking(s, 0 , 0);
return result;
}
};
Leecode 78. 子集
题目描述
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
- 示例 1:
输入:
nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
- 示例 2:
输入:
nums = [0]
输出:[[],[0]]
迭代法求解
虽然这部分学习的都是回溯,但是本题我最先想出来的解法却是使用迭代法来求解。
思考如何构造一个集合中的子集且不重复,首先一开始有一个初始的空集,存放到一个用于存放子集的结果族中(族,表示是由集合组成的集合),随后开始逐个选取集合中的元素(记作元素i)。每次取到的元素i都是当前结果族中所有集合中都没有的全新元素,那么此时如果要考虑包含这个新的元素的子集,就将目前结果族中的所有集合都复制一份的同时往里面添加当前这个新元素,再把复制得到的这些集合全部存入子集族中。只需要使用同样的方法,遍历完集合中的所有元素,我们就完成了不重复地构造子集。
具体可以写出代码如下所示:
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> result;
vector<int> curVec;
result.push_back(curVec); // 存入空集
for(int i = 0; i < nums.size(); i++){ // 对于nums中的每一个新的、还没考虑过包含这个元素的子集的元素
int size = result.size(); // 记录目前已有的子集的个数
for(int j = 0; j < size; j++){ // 在目前已经存放的所有子集中都加上这个元素再进行一次存放
vector<int> newVec = result[j]; // 遍历已有的所有子集,都复制一份
newVec.push_back(nums[i]); // 将复制的子集中新增一个元素
result.push_back(newVec); // 将得到的新数组存入结果当中
}
}
return result;
}
};
注意到上面算法中,每一次都将原本的子集族中的元素复制一份并全部添加新元素,就相当于乘以一次2,最终对于\(X\)个元素的集合,就会有\(2^X\)个子集。而这也与数学集合论中幂集的个数相同。
回溯法
本题的回溯法也非常简单,和之前几道组合的题目非常类似,区别在于本题的子集是需要取到树中的每一个节点,而组合是只取树的叶节点。既然是要取到所有节点,那么只需将原本组合中存放节点时的条件判断去掉即可。故我们可以得到如下代码:
class Solution {
public:
vector<vector<int>> result;
vector<int> curVec;
void backTracking(vector<int>&nums, int start){
result.push_back(curVec); // 每一个节点都进行存放,不需要条件判断
for(int i = start; i < nums.size(); i++){
curVec.push_back(nums[i]); // 将当前节点的数存入自己
backTracking(nums, i+1); // 递归
curVec.pop_back(); // 回溯
}
}
vector<vector<int>> subsets(vector<int>& nums) {
backTracking(nums, 0);
return result;
}
};
Leecode 90. 子集 II
题目描述
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的 子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
- 示例 1:
输入:
nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]
- 示例 2:
输入:
nums = [0]
输出:[[],[0]]
使用集合set容器进行回溯
本题与上题的不同在于一开始给定的集合中有重复元素,意味着子集中也可以出现重复元素。但是最终存放到vector<vector>中的子集却不能重复。为此可以考虑先使用一个sec<vector> 来存放所有子集,再将其转换为vector,其余操作均和上一题一致。那么本题可以写作:
class Solution {
public:
set<vector<int>> result;
vector<int> curVec;
void backTracking(vector<int>& nums, int start){
result.insert(curVec);
for(int i = start; i < nums.size(); i++){
curVec.push_back(nums[i]);
backTracking(nums, i+1);
curVec.pop_back();
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(), nums.end());
backTracking(nums, 0);
vector<vector<int>> re(result.begin(), result.end());
return re;
}
};
这样的做法和上一题完全一致。只是先用set后再将其转换为vector,从而进行去重。接下来再尝试使用start相关的判断来去重。
使用start判断去重
本题要去重的是值相等时不同组合的情况。即例如下面输入测试案例:
输入:
[1, 2, 2];
如果不进行去重判断,则会依次存入:[] -> [1] -> [1, 2] -> [1, 2, 2] -> [1, 2] -> ...注意此时就存入了两个[1,2]。
上面例子中出现了两个的[1,2],其中第一个[1,2]中的2来自于原始集合中的第一个2,第二个[1,2]中的2来自于原始集合中的第二个2。为了避免这样的重复结果存入,可以在for循环中加入下面判断进行去重:
if(i != start && nums[i] == nums[i-1] ) continue; // 当有重复元素出现时,只有当其为第一次出现时才进行保留,否则直接跳过
上面注意到上面判断中,在判断是否重复之前加了一条i != start的判断,原因是为了保证该元素第一次出现时的子集结果能够存入。对应上面列举出的例子,也就是为了确保[1,2,2]子集能够被存入,而不是看到重复元素就直接跳过。
在将上面if判断语句加入for循环后,可以得到代码如下:
class Solution {
public:
vector<vector<int>> result;
vector<int> curVec;
void backTracking(vector<int>& nums, int start){
result.push_back(curVec);
for(int i = start; i < nums.size(); i++){
if(i != start && nums[i] == nums[i-1] ) continue;
curVec.push_back(nums[i]);
backTracking(nums, i+1);
curVec.pop_back();
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(), nums.end());
backTracking(nums, 0);
return result;
}
};
这样使用上面代码即可完成去重操作。
今日总结
这第二十四天的打卡实际上晚了两天,原本该在4.18完成的但是拖到了4.20。最近马上有两门考试,所以可能Leecode打卡会不太及时,但是也会尽量检查全部跟上刷完的,
当前力扣已刷88题,再接再励!
代码随想录第二十四天 | Leecode 93. 复原IP地址 、78. 子集、 90.子集II的更多相关文章
- LeetCode算法训练 93.复原IP地址 78.子集 90.子集II
欢迎关注个人公众号:爱喝可可牛奶 LeetCode算法训练 93.复原IP地址 78.子集 90.子集II LeetCode 93. 复原 IP 地址 分析 字符串全部由数字组成,ipv4每一段数字不 ...
- Java实现 LeetCode 93 复原IP地址
93. 复原IP地址 给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式. 示例: 输入: "25525511135" 输出: ["255.255.11. ...
- leetcode 93 复原IP地址
IP地址,分成四段,每段是0-255,按照每段的长度分别为1,2,3下一段长度分别1,2,3再下一段......进行递归遍历,能满足条件的假如res中.比较难想到的就是假如有一段是三位的010是不符合 ...
- leetcode刷题-93复原IP地址
题目 给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式. 有效的 IP 地址正好由四个整数(每个整数位于 0 到 255 之间组成),整数之间用 '.' 分隔. 示例: 输入: &q ...
- 93. 复原 IP 地址
做题思路or感想 这种字符串切割的问题都可以用回溯法来解决 递归三部曲: 递归参数 因为要切割字符串,所以要用一个startIndex来控制子串的开头位置,即是会切割出一个范围是[startIndex ...
- 【每日一题】【回溯】2021年12月29日-93. 复原 IP 地址
有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔. 例如:"0.1.2.201" 和 "192.1 ...
- 【iCore3 双核心板】例程二十四:LAN_DHCP实验——动态分配IP地址
实验指导书及代码包下载: http://pan.baidu.com/s/1i4vMMv7 iCore3 购买链接: https://item.taobao.com/item.htm?id=524229 ...
- 【iCore4 双核心板_ARM】例程二十四:LWIP_DHCP实验——动态分配IP地址
实验现象: 核心代码: int main(void) { system_clock.initialize(); led.initialize(); adc.initialize(); delay.in ...
- LeetCode 93. 复原IP地址(Restore IP Addresses)
题目描述 给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式. 示例: 输入: "25525511135" 输出: ["255.255.11.135&qu ...
- 93. 复原IP地址
题目描述: 给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式. 示例: 输入: "25525511135" 输出: ["255.255.11.135&q ...
随机推荐
- FLink13--KeyByMaxByApp
一.依赖 https://www.cnblogs.com/robots2/p/16048648.html 二.代码 package net.xdclass.class9; import java.ut ...
- P4774 [NOI2018] 屠龙勇士 题解
传送门 题解 思路 由题目可知,一条龙被攻击 \(x\) 次并回复若干次后生命值恰好为 \(0\) 则死亡,可以得出如下式子: \[\large ATK_i \cdot x \equiv a_i(\m ...
- Typecho自定义右键菜单美化和禁用F12
右键美化 使用右键美化,请禁用 HoerMouse 鼠标美化插件,否则貌似没效果 Joe主题在后台-外观设置-设置外观-全局设置-自定义<body></body>标签内填入如下 ...
- docker - [15] springboot微服务打包docker镜像
步骤: 1.构建Springboot项目 2.打包应用 3.编写dockerfile 4.构建docker镜像 5.发布运行 一.构建Springboot项目 (1)创建一个SpringBoot(以下 ...
- hbase - [06] rowkey的设计
HBase是三维有序存储的,通过rowkey(行键),column key(column family和qualifier)和TimeStamp(时间戳)这三个维度可以对HBase中的数据进行快速定位 ...
- Elasticsearch7.8搭建(Windows版本单节点、Linux版本单节点、集群,)
The Elastic Stack, 包括 Elasticsearch.Kibana.Beats 和 Logstash(也称为 ELK Stack).能够安全可靠地获取任何来源.任何格式的数据,然后实 ...
- HTTP协议与RESTful API实战手册(终章):构建企业级API的九大秘籍 🔐
title: HTTP协议与RESTful API实战手册(终章):构建企业级API的九大秘籍 date: 2025/2/28 updated: 2025/2/28 author: cmdragon ...
- 机器学习 | 强化学习(7) | 融合学习与规划(Integrating Learning and Planning)
7-融合学习与规划(Integrating Learning and Planning) 1.导论 基于模型的强化学习(Model-Based Reinforcement Learning) 在上一个 ...
- Cordova基本使用(二)
cordova的打包发布版app流程简介 除了第一遍官网给的打包发布版的方法,我们可以自己多敲几次命令来实现. 基本上使用如下的几个命令就完成这个过程,先列出整个过程: 1.cordova选定ando ...
- 解决macOS无法验证“xxx”的开发者。你确定要打开它吗?
前言 当 macOS 无法验证开发者时,有两种方式解决,可以通过以下步骤来打开 xxx 系统偏好设置: 打开"系统偏好设置". 选择"安全性与隐私". 在&qu ...
