代码随想录算法训练营第二十八天|93.复原IP地址、78.子集、90.子集II
93.复原IP地址
问题简述:为一串数字添加.
分割成有效的ip地址,输出所有的可能。
思考:这个感觉和上一道回文串很像,就是判断回文串改为判断数字是否有效。不过还是有很多细节需要注意。
算法思路:
-
定义函数用双指针判断字符串的
[left,right]
范围数字是否有效,如果长度大于3则无效;如果子串以0开头,且不是单个0,视为无效的IP地址段;将字符转换为数字,如果在[0,255]
则有效,否则无效。 -
定义lists存储所有组合,定义path存储当前分割的数字。startidx为当前指向的字符。
-
递归函数中如果当前path中数字大于四个则直接返回;如果等于四个,且遍历完了s,则将ip加入lists,加入时用
.
来间隔。依次从startIdx遍历所有元素,每次判断startIndex到当前i是否有效,如果有效则将这一段加入path,然后继续递归这一段之后的字符串后,进行回溯,即将path最后的数字移除。
import java.util.ArrayList;
import java.util.List;
class Solution {
//定义返回值
List<String> lists = new ArrayList<>();
List<String> path = new ArrayList<>();
public List<String> restoreIpAddresses(String s) {
backtracking(s, 0);
return lists;
}
//回溯函数
public void backtracking(String s, int startidx){
//如果path中数字个数大于4直接返回
if (path.size() > 4) return;
//如果path中数字个数等于4且遍历完了s,则将ip加入lists
if (path.size() == 4 && startidx >= s.length()){
StringBuilder sb = new StringBuilder(path.get(0));
for (int i = 1; i < 4; i++) {
sb.append('.');
sb.append(path.get(i));
}
lists.add(new String(sb));
}
//依次遍历每个元素,判断startidx到当前i元素的数字是否有效
for (int i = startidx; i < s.length(); i++) {
if (isValid(s, startidx, i)){
//如果有效则将数字加入进path,并递归再回溯,substring方法是加入左闭右开的字符
String str = s.substring(startidx, i + 1);
path.add(new String(str));
//递归
backtracking(s, i + 1);
//回溯
path.remove(path.size() - 1);
}
}
}
//判断是否有效,取left到right的左闭右闭区间
public boolean isValid(String s, int left, int right){
// 检查子串长度是否超过合理范围
if (right - left + 1 > 3) {
return false;
}
// 如果子串以0开头,且不是单个0,视为无效的IP地址段
if (left != right && s.charAt(left) == '0') {
return false;
}try {
int number = Integer.parseInt(s.substring(left, right + 1));
return number >= 0 && number <= 255;
} catch (NumberFormatException e) {
// 子串不是有效的整数表示
return false;
}
}
}
78.子集
问题简述:求出无重复集合的子集。
思考:之前的题都是在递归树的叶子结点保存值,这道题是在所有结点保存值。然后我单独添加了空集,其实也可以在递归内最先添加一下。
算法思路:
-
定义一个startIndex作为每次递归中candidates的起始位置;list存储每个结果集;result存储list的集合。
-
在主函数加入空集。
-
每次递归从startIndex将i指向的元素加入list,然后将list加入结果集中。再递归当前i的下一个元素,后进行回溯。
import java.util.ArrayList;
import java.util.List;
class Solution {
List<List<Integer>> result = new ArrayList<>();
List<Integer> list = new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
//加入空集
result.add(new ArrayList<>(list));
backtracking(nums, 0);
return result;
}
public void backtracking(int[] nums, int startIdx){
for (int i = startIdx; i < nums.length; i++) {
//加入当前元素
list.add(nums[i]);
//保留递归树中每个结点的值,即为每个子集
result.add(new ArrayList<>(list));
//递归i的下一个元素
backtracking(nums, i + 1);
//回溯
list.remove(list.size() - 1);
}
}
}
90.子集II
问题简述:求出有重复集合的子集。
思考:这道题关键在于去重,把两道题杂糅起来了。这个题当时有个疑问,我想知道去重那里条件的i>0是为了防止数组下标出现-1对吗,那这里为什么不可以把i>0替换为startidx>0呢,毕竟staetidx是i的起始位置,仍能保证i>0,我就因为这个位置找了好久bug。
- 答:可能有些时候idx等于0,但是i已经大于0了,这时候该跳过当前元素了,但是用
idx>0
就不能跳过了。
算法思路:
-
定义一个startIndex作为每次递归中candidates的起始位置;list存储每个结果集;result存储list的集合;定义一个used数组,长度为candidates长度,初始元素为0,used用来标记当前元素是否在path中,如果在的话变为1,进行回溯时变回0。
-
在主函数加入空集。
-
每次递归将startIndex指向的元素加入list,先进行去重,判断当前元素是否等于上一个元素,如果等于的话,还要判断used[i - 1]是否等于0,如果不为0说明上一个元素正在当前的list集合中;只有当used[i - 1] == 0时,我们已经确定上一个元素已经不在当前的list集合中,才可以进行去重。然后遍历所有元素,每次加入当前元素,更改used标志位,然后进行递归下一个位置元素,最后进行回溯,更改used,并推出list最后的元素。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class Solution {
List<List<Integer>> result = new ArrayList<>();
List<Integer> list = new ArrayList<>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
//加入空集
result.add(new ArrayList<>(list));
//进行排序为了去重
Arrays.sort(nums);
//标记使用
int[] used = new int[nums.length];
backtracking(nums, 0, used);
return result;
}
public void backtracking(int[] nums, int startIdx, int[] used){
for (int i = startIdx; i < nums.length; i++) {
//去重
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == 0) continue;
//加入当前元素
list.add(nums[i]);
used[i] = 1;
//保留递归树中每个结点的值,即为每个子集
result.add(new ArrayList<>(list));
//递归i的下一个元素
backtracking(nums, i + 1, used);
//回溯
used[i] = 0;
list.remove(list.size() - 1);
}
}
}
感想
诶,遇到问题想不明白就很难再继续下去,总是要挺一段时间,周围的很多人都找到工作了,虽然不是技术岗吧,我还要继续坚持吗?感觉状态不行。