93.复原IP地址
本期本来是很有难度的,不过 大家做完 分割回文串 之后,本题就容易很多了
题目链接/文章讲解:代码随想录
视频讲解:回溯算法如何分割字符串并判断是合法IP?| LeetCode:93.复原IP地址_哔哩哔哩_bilibili
有效 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
中的任何数字。你可以按 任何 顺序返回答案。
class Solution {
List<String>res=new ArrayList<>();
public List<String> restoreIpAddresses(String s) {
StringBuilder st=new StringBuilder(s);//将s转化为可以增删的字符串
back(st,0,0);
return res;
}
public void back(StringBuilder s,int index,int num)
{
if(num==3&&isvalid(s,index,s.length()-1))
{
res.add(s.toString());
return;
}
for(int i=index;i<s.length();i++)
{
if(isvalid(s,index,i)){//实际上已经是一个剪枝的操作,若此段不符合要求,则后续回溯操作不必进行
s.insert(i+1,'.');//添加点的步骤
back(s,i+2,num+1);//back(StringBuilder s,int index,int num),index更新为i+2,表示从剩余的元素(索引为index+2)中开始选切割
s.deleteCharAt(i+1);//删去i+1索引的这个点,便于选择另外一个方向
}
else
{
break;
}
}}
//[start,end]为左闭右闭,为每一个分割开的字符,适用于StringBuilder
public boolean isvalid(StringBuilder s,int start,int end)
{
if(start>end)
{
return false;
}
String seg=s.substring(start,end+1);//左闭右开
if(seg.length()>3)
{
return false;
}
int num=Integer.parseInt(seg);
if(num>255||num<0)
{
return false;
}
//前导0
if(seg.charAt(0)=='0'&&seg.length()>1)
{
return false;
}
return true;
}
}
注意点:
问题 | 说明 |
---|---|
"9245587303" | 长度太长,不可能是合法 IP 段(超过 255) |
Integer.parseInt() | 只能处理合法 int 范围(-2,147,483,648 到 2,147,483,647) |
.substring(start, end+1) | 如果不加长度限制,可能会生成很长子串导致异常 |
在使用Integer.parseInt()之前,为避免长度太长,应该提前筛去length>3的切割字符串
insert() | StringBuilder | 向字符串中插入字符 | sb.insert(1, ".") |
add() | List 接口实现类 | 向列表中添加元素 | list.add("192") |
方法名 | 适用类型 | 用途 |
---|---|---|
deleteCharAt(int index) | StringBuilder / StringBuffer | 删除指定位置的字符 |
remove(int index) | List (如 ArrayList ) | 删除集合中指定索引的元素 |
remove(Object o) | List , Set 等 | 删除集合中指定值的元素 |
String的方法大全
length() | 获取字符串长度 | "abc".length() → 3 |
charAt(int index) | 获取指定位置的字符 | "hello".charAt(1) → 'e' |
substring(int begin) | 从索引开始截取到末尾 | "hello".substring(2) → "llo" |
substring(int begin, int end) | 截取部分字符串 [begin, end) | "hello".substring(1, 4) → "ell" |
indexOf(String str) | 查找子串首次出现的位置 | "apple".indexOf("p") → 1 |
lastIndexOf(String str) | 查找子串最后出现位置 | "apple".lastIndexOf("p") → 2 |
contains(String s) | 是否包含子串 | "abc".contains("b") → true |
isEmpty() | 是否为空字符串(长度为 0) | "".isEmpty() → true |
equals(String s) | 判断是否相等(区分大小写) | "abc".equals("ABC") → false |
equalsIgnoreCase(String s) | 忽略大小写比较 | "abc".equalsIgnoreCase("ABC") → true |
toLowerCase() / toUpperCase() | 转小写 / 转大写 | "Hello".toLowerCase() → "hello" |
trim() | 去除首尾空格 | " hi ".trim() → "hi" |
方法 | 用法 | 说明 |
---|---|---|
String.valueOf(任意类型) | String.valueOf(123) → "123" | 将任意值转为字符串 |
Integer.parseInt(String s) | "123" → 123 | 将字符串转为整数(注意捕获异常) |
Double.parseDouble(String s) | "3.14" → 3.14 | 将字符串转为小数 |
startsWith(String prefix) | "hello".startsWith("he") → true | 是否以指定前缀开始 |
endsWith(String suffix) | "hello".endsWith("lo") → true | 是否以指定后缀结束 |
matches(String regex) | "123".matches("\\d+") → true | 是否符合正则表达式 |
char[] arr = "hello".toCharArray(); // 字符串 → 字符数组
String s = new String(arr); // 字符数组 → 字符串
解法:
画出回溯的树形结构来分析,
78.子集
子集问题,就是收集树形结构中,每一个节点的结果。 整体代码其实和 回溯模板都是差不多的。
题目链接/文章讲解:https://programmercarl.com/0078.%E5%AD%90%E9%9B%86.html
视频讲解:回溯算法解决子集问题,树上节点都是目标集和! | LeetCode:78.子集_哔哩哔哩_bilibili
class Solution {
List<List<Integer>>res=new ArrayList<>();//注意接口和实现类
LinkedList<Integer>path=new LinkedList<>();
public List<List<Integer>> subsets(int[] nums) {
back(nums,0);
return res;
}
public void back(int []nums,int index)
{
res.add(new LinkedList<>(path));
for(int i=index;i<nums.length;i++)
{
path.add(nums[i]);
back(nums,i+1);
path.removeLast();
}
}
}
空集的产生:在第一次调用 back(nums, 0)
时,path
是空的,res.add(new LinkedList<>(path))
就把空集 []
加入到结果中。
90.子集II
题目链接/文章讲解:代码随想录
视频讲解:回溯算法解决子集问题,如何去重?| LeetCode:90.子集II_哔哩哔哩_bilibili
class Solution {
List<List<Integer>> result = new ArrayList<>();// 存放符合条件结果的集合
LinkedList<Integer> path = new LinkedList<>();// 用来存放符合条件结果
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);//去重前应该先排序
back(nums, 0);
return result;
}
private void back(int[] nums, int startIndex){
result.add(new ArrayList<>(path));//「遍历这个树的时候,把所有节点都记录下来,就是要求的子集集合」。
if (startIndex >= nums.length){ //终止条件可不加
return;
}
for (int i = startIndex; i < nums.length; i++){
if(i>startIndex&&nums[i]==nums[i-1]){continue;}//判断i>startIndex避免出现-1
path.add(nums[i]);
back(nums, i + 1);
path.removeLast();
}
}
}
先排序,再进行经典去重操作