93. 复原 IP 地址
class Solution {
//画图理解
public List<String> restoreIpAddresses(String s) {
//定义表示一个字符长度的变量
int len = s.length();
//定义一个返回结果的集合
List<String> res = new ArrayList<>();
//如果当前字符长度大于12或者小于4都不满足
if(len > 12 || len <4){
return res;
}
//定义一个保存路径上的变量
Deque<String> path = new ArrayDeque<>();
//深度优先搜索
dfs(s,len, 0, 4, path, res);
//返回结果
return res;
}
public void dfs(String s, int len, int begin, int residue, Deque<String> path, List<String> res){
//如果字符串已经遍历到最后了,并且已经切分为4段了,
//就把当前路径上的元素加入到返回的结果集中
if(begin == len){
if(residue ==0){
res.add(String.join(".", path));
}
return;
}
//begin表示遍历字符串从哪里开始
for(int i = begin; i < begin+3; i++){
//如果超出字符串的长度,就直接退出
//begin,每次选择都是从之前选择的元素的下一个元素开始,
if(i >= len){
break;
}
//如果剩余元素大于ip最多能容纳的个数,就剪枝。
if(len -i > residue * 3){
continue;
}
//判断当前截取字符是否是小于0或者大于255
//这里的begin和i,代表的是,这时候截取了几个字符
//begin从哪里开始,i到哪里结束
if(judgeIpSegment(s, begin, i)){
//保留当前截取字符
String currentIpSegment = s.substring(begin, i+1);
//将当前路径上的元素加入到路径队列中
path.addLast(currentIpSegment);
//递归下一层
dfs(s, len, i+1,residue -1, path, res);
//剪枝
path.removeLast();
}
}
}
private boolean judgeIpSegment(String s, int left, int right){
//定义一个表示整个字符的长度
int len = right - left +1;
//如果截取的大于等于2的字符的开头为0,就直接false
if(len > 1 && s.charAt(left) == '0'){
return false;
}
//定义返回结果的集合
int res = 0;
//拼接字符
while(left <= right){
//res*10 是为了将先加的字符默认比后面的字符大10倍,也就是位数是从小到大
res = res * 10 + s.charAt(left) - '0';
left++;
}
return res >= 0 && res <= 255;
}
}
1、一开始,字符串的长度小于 4 或者大于 12 ,一定不能拼凑出合法的 ip 地址(这一点可以一般化到中间结点的判断中,以产生剪枝行为);
2、每一个结点可以选择截取的方法只有 3 种:截 1 位、截 2 位、截 3 位,因此每一个结点可以生长出的分支最多只有 3 条分支;
根据截取出来的字符串判断是否是合理的 ip 段,这里写法比较多,可以先截取,再转换成 int ,再判断。我采用的做法是先转成 int,是合法的 ip 段数值以后,再截取。
3、由于 ip 段最多就 4 个段,因此这棵三叉树最多 4 层,这个条件作为递归终止条件之一;
4、每一个结点表示了求解这个问题的不同阶段,需要的状态变量有:
splitTimes:已经分割出多少个 ip 段;
begin:截取 ip 段的起始位置;
path:记录从根结点到叶子结点的一个路径(回溯算法常规变量,是一个栈);
res:记录结果集的变量,常规变量。
作者:liweiwei1419
链接:https://leetcode.cn/problems/restore-ip-addresses/solutions/100433/hui-su-suan-fa-hua-tu-fen-xi-jian-zhi-tiao-jian-by
回溯部分的题解看liweiwei大神的就可以了,感觉比卡哥的容易一些。
78.子集
class Solution {
List<List<Integer>> res = new ArrayList<>();
ArrayDeque<Integer> path = new ArrayDeque<>();
public List<List<Integer>> subsets(int[] nums) {
backtracking(nums, 0);
return res;
}
private void backtracking(int[] nums, int startIndex){
res.add(new ArrayList<>(path));
if(startIndex >= nums.length) return;
// res.add(new ArrayList<>(path));
for(int i = startIndex; i < nums.length; i++){
path.addLast(nums[i]);
backtracking(nums, i + 1);
path.removeLast();
}
}
}
startIndex
:表示当前递归层级开始的索引。它用来控制从哪个位置开始选择元素。i
:在循环中,表示当前正在处理的元素的索引。- 假设
nums = [1, 2, 3]
,第一次调用backtracking(nums, 0)
时,startIndex = 0
。 - 选择
1
后,递归调用backtracking(nums, 1)
(startIndex + 1
),这将导致在下一个递归中只考虑2
和3
,而无法再次选择1
。
90.子集II
class Solution {
List<List<Integer>> res = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> subsetsWithDup( int[] nums ) {
Arrays.sort( nums );
subsetsWithDupHelper( nums, 0 );
return res;
}
private void subsetsWithDupHelper( int[] nums, int start ) {
res.add( new ArrayList<>( path ) );
for ( int i = start; i < nums.length; i++ ) {
// 跳过当前树层使用过的、相同的元素
if ( i > start && nums[i - 1] == nums[i] ) {
continue;
}
path.add( nums[i] );
subsetsWithDupHelper( nums, i + 1 );
path.removeLast();
}
}
}