#Java #回溯
Feeling and experiences:
有效 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> ans = new ArrayList<>();
List<String> path = new ArrayList<>();
public List<String> restoreIpAddresses(String s) {
//思路和分割回文串相似
if(s.length() == 0){
return new ArrayList<>();
}
back(s,0);
return ans;
}
public void back(String s,int startIndex){
//终止条件
if(path.size() > 4){
return;
}
if(path.size() == 4 && startIndex >= s.length()){
String temp = String.join(".",path);
ans.add(temp);
return;
}
for(int i = startIndex;i<s.length();i++){
if(isValid(s,startIndex,i)){
String str = s.substring(startIndex,i+1);
path.add(str);
}
else{
continue;
}
back(s,i+1);
path.remove(path.size()-1);
}
}
//判断字符串在区间[start,end]是否合法
public boolean isValid(String s , int start,int end){
if(start > end){
return false;
}
//不能有前导0
if(s.charAt(start) == '0' && start != end){
return false;
}
//不能有非数字
int num = 0;
for(int i = start; i<=end;i++){
if(s.charAt(i) > '9' || s.charAt(i) < '0'){
return false;
}
//不能大于255
num = num * 10 + (s.charAt(i) - '0');
if(num > 255){
return false;
}
}
return true;
}
}
与分割回文串的差别:
终止条件:
? 如果path中的段数超过4,说明不是有效的IP地址,直接返回。
? 如果path中的段数等于4且遍历完了整个字符串s,则将path中的段用点号连接起来,加入到ans列表中。
? 从startIndex开始,遍历字符串s的每个字符。
? 调用isValid函数判断当前切割的子串是否有效。如果有效,将其加入path中。
? 递归调用back函数,探索下一个字符。
? 回溯:完成对当前段的探索后,从path中移除最后加入的段。
还有就是字符串的合法条件(不难,但是多~有些地方容易漏掉)
给你一个整数数组?nums
,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
都是在回溯这一章节,那这道题又和之前的题有什么区别呢?
我感觉代码随想录中有一句话总结的很好:
子集是收集树形结构中树的所有节点的结果。
而组合问题、分割问题是收集树形结构中叶子节点的结果。
class Solution {
List<List<Integer>> ans = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
back(nums,0);
return ans;
}
public void back(int[] nums,int startIndex){
//终止条件
ans.add(new ArrayList<>(path));
if(startIndex >= nums.length){
return;
}
for(int i = startIndex;i<nums.length;i++){
path.add(nums[i]);
back(nums,i+1);
path.remove(path.size()-1);
}
}
}
这道题练习完,可以把它归为一道模板题
注意:终止条件中的if判断可以删除,(本来就是一个栈,最终会弹栈)
给你一个整数数组 nums
,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
?这道题在子集的基础又有运用了之前 组合II 问题的去重(可以说是,子集模板 + 去重模板)
class Solution {
List<List<Integer>> result = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
boolean[] used;
public List<List<Integer>> subsetsWithDup(int[] nums) {
if (nums.length == 0){
result.add(path);
return result;
}
Arrays.sort(nums);
used = new boolean[nums.length];
subsetsWithDupHelper(nums, 0);
return result;
}
private void subsetsWithDupHelper(int[] nums, int startIndex){
result.add(new ArrayList<>(path));
if (startIndex >= nums.length){
return;
}
for (int i = startIndex; i < nums.length; i++){
if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]){
continue;
}
path.add(nums[i]);
used[i] = true;
subsetsWithDupHelper(nums, i + 1);
path.removeLast();
used[i] = false;
}
}
}
同样的,可以不用used数组:
class Solution {
List<List<Integer>> result = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
if (nums.length == 0){
result.add(path);
return result;
}
Arrays.sort(nums);
subsetsWithDupHelper(nums, 0);
return result;
}
private void subsetsWithDupHelper(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;
}
path.add(nums[i]);
subsetsWithDupHelper(nums, i + 1);
path.removeLast();
}
}
}
江畔何人初见月?
江月何年初照人?
Fighting!