代码随想录算法训练营第二十八天 | 93. 复原IP地址,78. 子集,90.子集II

93. 复原IP地址

本期本来是很有难度的,不过 大家做完 分割回文串 之后,本题就容易很多了 

题目链接/文章讲解:代码随想录

视频讲解:回溯算法如何分割字符串并判断是合法IP?| LeetCode:93.复原IP地址_哔哩哔哩_bilibili

重点:

1. startIndex作为切割线

2. for循环,循环的是树的其中一层也就是当前切割线。递归,是递归到下一层,也就是下一根切割线

代码随想录算法训练营第二十八天 | 93. 复原IP地址,78. 子集,90.子集II_第1张图片

思路:

递归+回溯:

1. 确定参数及返回值

private void backtracking(String s, int startIndex, int pointSum)

2. 确定终止条件

判断当前画了几个点了,到了3个就切割完成,检查最后一部分是否满足IP要求。前面的部分在for循环的时候就检查了

if (pointSum == 3) {
    if (isValid(s, startIndex, s.length() - 1)) {
        result.add(s);
    }
    return;
}

3. 确定单层递归的逻辑

for循环移动的是当前的分割线,判断当前分割线有多少种可能。当前分割线出来的如果合法就可以固定这根分割线的情况下,递归去判断下一根分割线的情况了。backtracking(s, i + 2, pointSum),这里i+2是因为多加了点来分割了。

for (int i = startIndex; i < s.length(); i++) {
    // startIndex和i都是指切割线前一个
    if (isValid(s, startIndex, i)) {
        StringBuilder stringBuilder = new StringBuilder(s);
        stringBuilder.insert(i + 1, '.');
        s = stringBuilder.toString();
        pointSum++;
        backtracking(s, i + 2, pointSum);
        pointSum--;
        stringBuilder.deleteCharAt(i + 1);
        s = stringBuilder.toString();
    } else {
        break;
    }
}
List result;
public List restoreIpAddresses(String s) {
    result = new ArrayList<>();
    backtracking(s, 0, 0);
    return result;
}

private void backtracking(String s, int startIndex, int pointSum) {
    // 达到三根分割线,判断最后一部分是否满足IP要求
    if (pointSum == 3) {
        if (isValid(s, startIndex, s.length() - 1)) {
            result.add(s);
        }
        return;
    }
    // 当前层 - 不断移动当前这一根分割线
    for (int i = startIndex; i < s.length(); i++) {
        // startIndex和i都是指切割线前一个
        if (isValid(s, startIndex, i)) {
            StringBuilder stringBuilder = new StringBuilder(s);
            stringBuilder.insert(i + 1, '.');
            s = stringBuilder.toString();
            pointSum++;
            // 递归下一根分割线的情况
            backtracking(s, i + 2, pointSum);
            // 回溯回来
            pointSum--;
            stringBuilder.deleteCharAt(i + 1);
            s = stringBuilder.toString();
        } else {
            break;
        }
    }
}

private boolean isValid(String s, int start, int end) {
    // 左闭右闭区间
    if (start > end) {
        return false;
    }
    if (s.charAt(start) == '0' && start != end) {
        return false;
    }
    int num = 0;
    for (int i = start; i <= end; i++) {
        if (s.charAt(i) < '0' || s.charAt(i) > '9') {
            return false;
        }
        num = num * 10 + (s.charAt(i) - '0');
        if (num > 255) {
            return false;
        }
    }
    return true;
}

78. 子集

子集问题,就是收集树形结构中,每一个节点的结果。 整体代码其实和 回溯模板都是差不多的。 

题目链接/文章讲解:代码随想录

视频讲解:回溯算法解决子集问题,树上节点都是目标集和! | LeetCode:78.子集_哔哩哔哩_bilibili

重点:

1. 回溯算法的模板

2. 每层递归都把path加入到result

代码随想录算法训练营第二十八天 | 93. 复原IP地址,78. 子集,90.子集II_第2张图片

思路:

递归+回溯:

1. 确定参数及返回值

private void backtracking(int[] nums, int startIndex)

2. 确定终止条件

叶子节点终止,其实不写也可以,因为for循环自动终止

if (startIndex >= nums.length) { return; }

3. 确定单层递归的逻辑

一个for代表当前的一层,递归嵌套一个for就是代表下一层,在每个递归开始的时候收集path到result

// for循环代表树的某一层
for (int i = startIndex; i < nums.length; i++) {
    path.add(nums[i]);
    // 递归嵌套下一层for
    backtracking(nums, i + 1);
    // 回溯整理path
    path.remove(path.size() - 1);
}
List path;
List> result;
public List> subsets(int[] nums) {
    result = new ArrayList<>();
    path = new ArrayList<>();
    result.add(new ArrayList<>());
    backtracking(nums, 0);
    return result;
}

private void backtracking(int[] nums, int startIndex) {
    // 把path加入到result
    result.add(new ArrayList<>(path));
    // 终止条件
    if (startIndex >= nums.length) {
        return;
    }
    // for循环代表树的某一层
    for (int i = startIndex; i < nums.length; i++) {
        path.add(nums[i]);
        // 递归嵌套下一层for
        backtracking(nums, i + 1);
        // 回溯整理path
        path.remove(path.size() - 1);
    }
}

90.子集II

大家之前做了 40.组合总和II 和 78.子集 ,本题就是这两道题目的结合,建议自己独立做一做,本题涉及的知识,之前都讲过,没有新内容。 

题目链接/文章讲解:代码随想录

视频讲解:回溯算法解决子集问题,如何去重?| LeetCode:90.子集II_哔哩哔哩_bilibili

重点:

1. 树层去重

2. 每次递归都把path加入到result

代码随想录算法训练营第二十八天 | 93. 复原IP地址,78. 子集,90.子集II_第3张图片

思路:

递归+回溯:

1. 确定参数及返回值

private void backtracking(int[] nums, int startIndex, Boolean[] used)

2. 确定终止条件

其实不写终止条件也可以,因为for循环根本不可能超过nums.length

if (startIndex == nums.length) { return; }

3. 确定单层递归的逻辑

used数组确定是否是树层

for (int i = startIndex; i < nums.length; i++) {
    if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
        continue;
    }
    path.add(nums[i]);
    used[i] = true;
    backtracking(nums, i + 1, used);
    path.remove(path.size() - 1);
    used[i] = false;
}
List path;
List> result;

public List> subsetsWithDup(int[] nums) {
    path = new ArrayList<>();
    result = new ArrayList<>();
    boolean[] used = new boolean[nums.length];
    Arrays.sort(nums);
    backtracking(nums, 0, used);
    return result;
}

private void backtracking(int[] nums, int startIndex, boolean[] used) {
    result.add(new ArrayList<>(path));
    if (startIndex == nums.length) {
        return;
    }
    for (int i = startIndex; i < nums.length; i++) {
        // nums[i] == nums[i - 1] && used[i - 1] == false意味着在同一层
        if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
            continue;
        }
        path.add(nums[i]);
        used[i] = true;
        backtracking(nums, i + 1, used);
        path.remove(path.size() - 1);
        used[i] = false;
    }
}

你可能感兴趣的:(算法,数据结构)