Leecode 93. 复原IP地址

题目描述

有效 IP 地址 正好由四个整数(每个整数位于 0255 之间组成,且不能含有前导 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来记录开始分割的位置
      • 在循环中每一次判断不合法则直接返回,如果合法则继续插入、递归、回溯

那么根据上面的思路不难写出下面代码:

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的更多相关文章

  1. LeetCode算法训练 93.复原IP地址 78.子集 90.子集II

    欢迎关注个人公众号:爱喝可可牛奶 LeetCode算法训练 93.复原IP地址 78.子集 90.子集II LeetCode 93. 复原 IP 地址 分析 字符串全部由数字组成,ipv4每一段数字不 ...

  2. Java实现 LeetCode 93 复原IP地址

    93. 复原IP地址 给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式. 示例: 输入: "25525511135" 输出: ["255.255.11. ...

  3. leetcode 93 复原IP地址

    IP地址,分成四段,每段是0-255,按照每段的长度分别为1,2,3下一段长度分别1,2,3再下一段......进行递归遍历,能满足条件的假如res中.比较难想到的就是假如有一段是三位的010是不符合 ...

  4. leetcode刷题-93复原IP地址

    题目 给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式. 有效的 IP 地址正好由四个整数(每个整数位于 0 到 255 之间组成),整数之间用 '.' 分隔. 示例: 输入: &q ...

  5. 93. 复原 IP 地址

    做题思路or感想 这种字符串切割的问题都可以用回溯法来解决 递归三部曲: 递归参数 因为要切割字符串,所以要用一个startIndex来控制子串的开头位置,即是会切割出一个范围是[startIndex ...

  6. 【每日一题】【回溯】2021年12月29日-93. 复原 IP 地址

    有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔. 例如:"0.1.2.201" 和 "192.1 ...

  7. 【iCore3 双核心板】例程二十四:LAN_DHCP实验——动态分配IP地址

    实验指导书及代码包下载: http://pan.baidu.com/s/1i4vMMv7 iCore3 购买链接: https://item.taobao.com/item.htm?id=524229 ...

  8. 【iCore4 双核心板_ARM】例程二十四:LWIP_DHCP实验——动态分配IP地址

    实验现象: 核心代码: int main(void) { system_clock.initialize(); led.initialize(); adc.initialize(); delay.in ...

  9. LeetCode 93. 复原IP地址(Restore IP Addresses)

    题目描述 给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式. 示例: 输入: "25525511135" 输出: ["255.255.11.135&qu ...

  10. 93. 复原IP地址

    题目描述: 给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式. 示例: 输入: "25525511135" 输出: ["255.255.11.135&q ...

随机推荐

  1. mac 安装ActiveMQ

    1.http://activemq.apache.org/activemq-5154-release.html 选gz 2.cd apache-activemq-5.15.4/bin/macosx 3 ...

  2. IDEA打开多个项目

    IDEA默认的情况下只能打开一个项目,即使添加了一个项目也会弹出一个窗口,将添加的项目显示在新的窗口中.通过下面操作可以,使IDEA打开过个项目. 1.1 打开项目结构 1.2 添加多个项目 点击&q ...

  3. druid 连接池参数说明

    一.参数配置说明 属性 说明 建议值 url 数据库的jdbc连接地址.一般为连接oracle/mysql.示例如下:     mysql : jdbc:mysql://ip:port/dbname? ...

  4. Atcoder ABC390E Vitamin Balance 题解 [ 绿 ] [ 背包 ] [ 二分 ]

    Vitamin Balance:比较板的背包. 思路 一个 dp 数组里同时存三种食物的最大维他命显然不可行,因为一种食物维他命最多不代表其他维他命也同样多,而最终的价值取决于维他命最少的那个,所以这 ...

  5. 2023LN省选游记

    前言 CSP第一轮都考完了,我才写这个游记.我真懒惰 书接上回 正文 Day -114514 我也没想到我居然能报省选. 报上了.准备去爆零. Day -114513~Day -1 学习暴力算法以及痛 ...

  6. stay:将代码翻译为Gif动图,妈妈再也不用担心我调试找不到bug了

    本文首发于微信公众号:呼哧好大枫.原作者与本文作者是同一人. 平常在做算法题或者是 debug 的时候很需要一款能够实时地将代码执行逻辑和数据以图形化的形式渲染出来的工具.之前尝试了几款(visual ...

  7. 关于DateTime的自定义转换

    关于DateTime的自定义转换.把字符串时间转换成可以供DateTime类型识别的字符串类型的粗略实现. /// <summary> /// 把从数据库中读取的字符串的CurrentTi ...

  8. 从龟速乘到 $Miller-Rabin$ 算法(数论算法总结)

    发现自己竟然菜到不太会龟速乘,所以把 \(Miller-Rabin\) 算法所需要用到的算法全学了一遍-- 龟速乘 龟速乘是一种 \(O(\log n)\) 的乘法计算方法. 考虑有时普通乘法取模会爆 ...

  9. WPF .Net Core 3.1遇到Satellite Assemblies无法正常加载的处理

    1.原因 加载的时候没有调取 AssemblyLoadContext.Default 2.解决方案: 在程序启动的时候,手动调用 /// <summary> /// Interaction ...

  10. 洛谷P4390 [BalkanOI2007] Mokia 摩基亚 题解

    题目传送门. 想必 我的另外一篇题解 已经把这道题的思路说的很清楚了,但是那道题是把所有的修改全部告诉你,然后再一个一个问你矩阵和,但是这道题他是修改中夹着询问,但是没有关系,我们照样可做. 考虑将所 ...