本题和LeetCode131.分割回文串都是属于切割字符串问题中,比较复杂的题目。本题的难点:如何模拟切割的操作、如何找到切割点、判断子串是否是合法的IP地址、如何将合法的子串拼接。
确定递归参数:
由于本题的结果集中是要求我们输出正确的IP地址,这时就要求我们在切割出正确的字符串子串后,需要对原字符串进行添加“.”操作;如果按照上述说法,每次切割出正确的都添加“.”看起来好像没有什么问题,但是当我们切完整个字符串后,会发现字符串的最后多一个“.”,此时就需要我们控制添加次数,当我们添加三次后,直接判断剩余的字符是否符合正确的IP地址的格式。
void backtracking(String s, int startindex, int pointsum) {
}
确定终止条件:
上文提到了,当我们的逗点个数等于三,直接判断剩余的字符是否符合正确的IP地址的格式。
if (pointsum == 3) {
if (trueIp(s, startindex, s.length()-1)) {
result.add(s);
}
return ;
}
单层递归逻辑:
//单层递归逻辑
for (int i = startindex; i < s.length(); i++) {
if (trueIp(s, startindex, i)) {//当前的子串符合ip的规范,进行插入.
s = s.substring(0, i+1)+'.'+s.substring(i + 1, s.length());
pointsum++;
backtracking(s, i+2, pointsum);
s = s.substring(0, i+1) + s.substring(i + 2, s.length());
pointsum--;
}else {
break ;
}
}
判断IP地址是否合法:
//判断字符串s在区间左闭右闭所组成的数字是否合法
boolean trueIp(String s, int start, int end) {//判断是否是合法ip地址
//合法的区间
if (start > end) {
return false;
}
//子串不能含有前导0(子串的长度不为1)
if (s.charAt(start) == '0' && start != end) {
return false;
}
int sum = 0;
for (int i = start; i <= end; i++) {
//子串中的字符 要在0-9之间
if (s.charAt(i) < '0' || s.charAt(i) > '9') {
return false;
}
//子串中最大不超过255
sum = sum * 10 + (s.charAt(i) - '0');
if (sum > 255) {
return false;
}
}
return true;
}
完整代码:
class Solution {
List result = new ArrayList<>();
public List restoreIpAddresses(String s) {
if (s.length() > 12 || s.length() < 4) {
return result;
}
backtracking(s, 0, 0);
return result;
}
void backtracking(String s, int startindex, int pointsum) {
if (pointsum == 3) {
if (trueIp(s, startindex, s.length()-1)) {
result.add(s);
}
return ;
}
//单层递归逻辑
for (int i = startindex; i < s.length(); i++) {
if (trueIp(s, startindex, i)) {//当前的子串符合ip的规范,进行插入.
s = s.substring(0, i+1)+'.'+s.substring(i + 1, s.length());
pointsum++;
backtracking(s, i+2, pointsum);
s = s.substring(0, i+1) + s.substring(i + 2, s.length());
pointsum--;
}else {
break ;
}
}
}
//判断字符串s在区间左闭右闭所组成的数字是否合法
boolean trueIp(String s, int start, int end) {//判断是否是合法ip地址
//合法的区间
if (start > end) {
return false;
}
//子串不能含有前导0(子串的长度不为1)
if (s.charAt(start) == '0' && start != end) {
return false;
}
int sum = 0;
for (int i = start; i <= end; i++) {
//子串中的字符 要在0-9之间
if (s.charAt(i) < '0' || s.charAt(i) > '9') {
return false;
}
//子串中最大不超过255
sum = sum * 10 + (s.charAt(i) - '0');
if (sum > 255) {
return false;
}
}
return true;
}
}
之前的组合问题,其实都是收集抽象树中的叶子节点,那本题是要求我们返回所有的子集,就是找抽象树的所有节点。
递归参数:
startindex 用来说明下一次取元素的位置
void backtracking(int[] nums, int startindex) {
}
终止条件:
因为本题是收集树中的所有节点,我们每一次递归都要收集。当startindex >= nums.length时整个数组都被遍历完。
result.add(new ArrayList<>(path));
if (startindex >= nums.length) {
return ;
}
单层递归逻辑:
void backtracking(int[] nums, int startindex) {
//收集所有节点
result.add(new ArrayList<>(path));
if (startindex >= nums.length) {
return ;
}
for (int i = startindex; i < nums.length; i++) {
path.add(nums[i]);
backtracking(nums, i+1);
path.remove(path.size() -1);
}
}
完整代码:
class Solution {
List> result = new ArrayList<>();
List path = new ArrayList<>();
public List> subsets(int[] nums) {
backtracking(nums, 0);
return result;
}
void backtracking(int[] nums, int startindex) {
//收集所有节点
result.add(new ArrayList<>(path));
if (startindex >= nums.length) {
return ;
}
for (int i = startindex; i < nums.length; i++) {
path.add(nums[i]);
backtracking(nums, i+1);
path.remove(path.size() -1);
}
}
}
本题和LeetCode78.子集的区别是,本题所给定的整数数组nums中的元素可以重复,并且我们输出到结果集中的单个集合不能重复。故本题和上一题的大体思路一样,只是多了一个去重操作。
class Solution {
List> result = new ArrayList<>();
List path = new ArrayList<>();
public List> subsetsWithDup(int[] nums) {
//因为给定的集合不一定是有序的,本题涉及到去重,先将数组中的元素用库函数去重
Arrays.sort(nums);
backtracking(nums, 0);
return result;
}
void backtracking(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]);
backtracking(nums, i +1);
path.remove(path.size() -1);
}
}
}