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"]
思路:本题与Day 27 回溯法 LeedCode:39. 组合总和 40.组合总和II 131.分割回文串-CSDN博客分割回文串思路相似,不同点在于,本题分割的同时需要加入".",用一个全局变量记录点的个数,当点的个数达到3时,判断剩余数字是否符合规范
判断数字是否符合规范:
public boolean isVaild(String s, int start, int end) {
//start>end,不合法
if (start > end)
return false;
//0开头的且不是0的数不合法
if (s.charAt(start) == '0' && start != end)
return false;
int sums = 0;
for (int i = start; i <= end; i++) {
//该字符不是数字,则不合法
if (s.charAt(i) > '9' || s.charAt(i) < '0') {
return false;
}
sums = 10 * (sums) + (s.charAt(i) - '0');
}
//大于255的数不合法
if (sums > 255) return false;
return true;
}
递归三部曲:
1.确定返回值和参数的类型:
用全局变量List<String> result = new ArrayList<>();记录结果
StringBuilder path = new StringBuilder();记录当前合法的ip地址
递归函数的返回值为void,参数传送需要分割的字符s,分割的起始位置start(int)
2.确定递归结束条件
if (num == 3) {
//点的个数等于3时,递归结束,判断剩下的数是否合法,如果合法则加入结果集
if (isVaild(s, start, s.length() - 1))
result.add(path.toString() + s.substring(start, s.length()));
return;
}
3.确定单层逻辑
用start表示当前数的开始,用i来控制当前分割的数的结束位置,如果分割出来的这个数是合法的,则缀在path后,用i+1当作下一个start继续递归往后分割,否则则改变i,重新分割出下一个数来判断
for (int i = start; i < s.length(); i++) {
if (!isVaild(s, start, i)) {
break;//当前划分出的数不符合规范,结束后面的划分
}
path.append(s.substring(start, i + 1) + ".");
num++;
backTracking(s, i + 1);//回溯
//把加上的字符串减去
path.delete(path.length() - i - 2 + start, path.length());
num--;
}
本题易错点:
本题在 这部分代码容易写错,如何删去之前加入的数字,这里需要好好思考,用本题的这种写法,进入下一层递归一定不能改变 path.length(),这是为什么我在判断第四个数是否合法时没有使用path.append()函数的原因
path.delete(path.length() - i - 2 + start, path.length());
总体代码参考:
class Solution {
List<String> result = new ArrayList<>();
StringBuilder path = new StringBuilder();
int num = 0;
public List<String> restoreIpAddresses(String s) {
if (s.length() > 12) return result;
backTracking(s, 0);
return result;
}
public void backTracking(String s, int start) {
if (num == 3) {
//点的个数等于3时,递归结束,判断剩下的数是否合法,如果合法则加入结果集
if (isVaild(s, start, s.length() - 1))
result.add(path.toString() + s.substring(start, s.length()));
return;
}
for (int i = start; i < s.length(); i++) {
if (!isVaild(s, start, i)) {
break;//当前划分出的数不符合规范,结束后面的划分
}
path.append(s.substring(start, i + 1) + ".");
num++;
backTracking(s, i + 1);
//把加上的字符串减去
path.delete(path.length() - i - 2 + start, path.length());
num--;
}
}
public boolean isVaild(String s, int start, int end) {
if (start > end)
return false;
if (s.charAt(start) == '0' && start != end)
return false;
int sums = 0;
for (int i = start; i <= end; i++) {
if (s.charAt(i) > '9' || s.charAt(i) < '0') {
return false;
}
sums = 10 * (sums) + (s.charAt(i) - '0');
}
if (sums > 255) return false;
return true;
}
}
这题比较容易错,可以打日志的形式来检查代码哪一环节出现了问题,比如检查 isVaild函数是否能正确判断合法...
78. 子集
给你一个整数数组 nums
,数组中的元素 互不相同 。返回该数组所有可能的
子集
(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3] 输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
思路:用回溯法经常用来实现分割(结果在每个叶节点),组合(结果在每个叶节点),子集(结果的每个结点)等问题,将找子集的回溯的过程抽象为遍历一棵树,那么结果集就是每个结点的集合
代码参考:
class Solution {
List<Integer> node=new LinkedList<>();
List<List<Integer>> result=new ArrayList();
public List<List<Integer>> subsets(int[] nums) {
result.add(new ArrayList<>());//先把空集合加入结果集
backTracking(nums,0);
return result;
}
void backTracking(int[]nums,int startIndex){
if(nums.length==startIndex){
return;
}
for(int i=startIndex;i<nums.length;i++){
node.add(nums[i]);
result.add(new ArrayList<>(node));//将叶节点都加入集合
backTracking(nums,i+1);//回溯
node.removeLast();
}
}
}
90. 子集 II
给你一个整数数组 nums
,其中可能包含重复元素,请你返回该数组所有可能的
子集
(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
示例 1:
输入:nums = [1,2,2] 输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]
代码参考:
思路:本题与上一题的区别就在于去重,Day 27 回溯法 LeedCode:39. 组合总和 40.组合总和II 131.分割回文串-CSDN博客本章中有讲到回溯的去重,与40.组合总和II的去重思想类似,使得解集不包含重复子集,我们应该对树层去重,而不是树的路径去重
本题的去重:把数组排序,让重复的数排在一起,当前数等于前一数,就跳过当前数,注意第一个数没有前一个数
图解
代码参考:
class Solution {
List<Integer> node=new LinkedList<>();
List<List<Integer>> result=new ArrayList();
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);//排序,让相同的排在一起,方便后面去重
result.add(new ArrayList<>());//先把空集合加入结果集
backTracking(nums,0);
return result;
}
void backTracking(int[]nums,int startIndex){
if(nums.length==startIndex){
return;
}
for(int i=startIndex;i<nums.length;i++){
if(i>startIndex&&nums[i-1]==nums[i]){
continue;//树层去重
}
node.add(nums[i]);
result.add(new ArrayList<>(node));//将叶节点都加入集合
backTracking(nums,i+1);//回溯
node.removeLast();
}
}
}