day28 ● 93.复原IP地址 ● 78.子集 ● 90.子集II

时间:2021-03-01 01:22:27

本文主要介绍三道LeetCode上的算法题目,分别是复原IP地址、子集、子集II。这三道题目均为经典的回溯算法题目,本文中将会详细介绍回溯算法的思想以及Java代码实现。

一、复原IP地址(题号:93)

1、题目描述

给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。

示例:

输入: “25525511135”

输出: [“255.255.11.135”, “255.255.111.35”]

2、解题思路

对于这道题目,我们可以采用回溯算法来解决。具体的思路如下:

(1)首先,我们需要定义好递归函数的参数。递归函数需要四个参数,分别表示当前已经处理的字符串的起始位置、当前已经处理的段数、当前已经处理好的IP地址以及最终结果。其中,最终结果是一个List类型的数据结构。

(2)递归函数中需要进行两个判断。第一个判断是,如果当前已经处理的段数已经等于4,那么我们就可以将当前已经处理好的IP地址添加到结果中,然后直接返回即可。第二个判断是,如果当前已经处理的字符串已经处理完了,那么我们也直接返回即可。

(3)接着,我们需要进行双重循环。第一重循环是从当前字符串的起始位置开始,到差不多结束的地方。第二重循环是从当前起始位置开始,到当前位置加上2的地方。在这个双重循环的过程中,我们需要不断地检查当前选择的字符串是否符合IP地址的要求。如果符合要求,那么我们就将这个字符串添加到当前已经处理好的IP地址中,然后递归调用自身,处理下一段,直到处理完所有的段。

3、Java代码实现

下面是Java代码的实现:

class Solution {
public List<String> restoreIpAddresses(String s) {
List<String> res = new ArrayList<>();
if (s == null || s.length() == 0 || s.length() > 12) {
return res;
}
dfs(s, 0, 0, new StringBuilder(), res);
return res;
}

private void dfs(String s, int start, int count, StringBuilder sb, List<String> res) {
    if (count == 4 && start == s.length()) {
        res.add(sb.toString().substring(0, sb.length() - 1));
        return;
    }
    if (count == 4 || start == s.length()) {
        return;
    }
    for (int i = start; i < s.length(); i++) {
        if (i - start >= 3) {
            return;
        }
        if (s.charAt(start) == '0' && i > start) {
            return;
        }
        String sub = s.substring(start, i + 1);
        if (Integer.parseInt(sub) > 255) {
            return;
        }
        dfs(s, i + 1, count + 1, sb.append(sub).append('.'), res);
        sb.delete(sb.length() - sub.length() - 1, sb.length());
    }
}
}

二、子集(题号:78)

1、题目描述

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例:

输入: nums = [1,2,3]

输出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]

2、解题思路

对于这道题目,我们也可以采用回溯算法来解决。具体的思路如下:

(1)首先,我们需要定义好递归函数的参数。递归函数需要五个参数,分别表示当前已经处理的位置、当前已经处理好的子集、原始数组、当前子集的长度以及最终结果。其中,最终结果是一个List<List>类型的数据结构。

(2)在递归函数中,我们需要进行两个判断。第一个判断是,如果当前已经处理好的子集长度等于原始数组的长度,那么说明已经处理完了,我们就可以将当前子集添加到最终结果中,然后直接返回即可。第二个判断是,如果当前处理的位置已经超过了原始数组的长度,那么我们也直接返回即可。

(3)接着,我们需要进行双重循环。第一重循环是从当前位置开始,到原始数组的长度。第二重循环是从当前位置的下一个位置开始,到原始数组的长度。在这个双重循环的过程中,我们需要不断地将选择的元素添加到当前已经处理好的子集中,然后递归调用自身,处理下一个位置。

3、Java代码实现

下面是Java代码的实现:

class Solution {
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
if (nums == null || nums.length == 0) {
return res;
}
dfs(nums, 0, new ArrayList<>(), 0, res);
return res;
}

private void dfs(int[] nums, int start, List<Integer> list, int len, List<List<Integer>> res) {
    if (len == nums.length) {
        res.add(new ArrayList<>(list));
        return;
    }
    if (start >= nums.length) {
        return;
    }
    list.add(nums[start]);
    dfs(nums, start + 1, list, len + 1, res);
    list.remove(list.size() - 1);
    dfs(nums, start + 1, list, len, res);
}
}

三、子集II(题号:90)

1、题目描述

给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例:

输入: [1,2,2]

输出:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]

2、解题思路

对于这道题目,我们同样可以采用回溯算法来解决。具体的思路如下:

(1)首先,我们需要定义好递归函数的参数。递归函数需要四个参数,分别表示当前已经处理的位置、当前已经处理好的子集、原始数组以及最终结果。其中,最终结果是一个List<List>类型的数据结构。

(2)在递归函数中,我们需要进行两个判断。第一个判断是,如果当前已经处理好的子集长度等于原始数组的长度,那么说明已经处理完了,我们就可以将当前子集添加到最终结果中,然后直接返回即可。第二个判断是,如果当前处理的位置已经超过了原始数组的长度,那么我们也直接返回即可。

(3)接着,我们需要进行双重循环。第一重循环是从当前位置开始,到原始数组的长度。第二重循环是从当前位置开始,到原始数组的长度。在这个双重循环的过程中,我们需要不断地将选择的元素添加到当前已经处理好的子集中,然后递归调用自身,处理下一个位置。需要注意的是,如果当前选择的元素和上一个选择的元素相同,那么我们就直接跳过即可。

3、Java代码实现

下面是Java代码的实现:

class Solution {
public List<List<Integer>> subsetsWithDup(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
if (nums == null || nums.length == 0) {
return res;
}
Arrays.sort(nums);
dfs(nums, 0, new ArrayList<>(), res);
return res;
}

private void dfs(int[] nums, int start, List<Integer> list, List<List<Integer>> res) {
    res.add(new ArrayList<>(list));
    for (int i = start; i < nums.length; i++) {
        if (i > start && nums[i] == nums[i - 1]) {
            continue;
        }
        list.add(nums[i]);
        dfs(nums, i + 1, list, res);
        list.remove(list.size() - 1);
    }
}
}

四、总结

以上就是本文对于三道经典回溯算法题目的详细介绍。回溯算法虽然看起来比较复杂,但实际上只需要掌握好其中的递归和回溯的思想,就可以比较轻松地解决这类问题。同时,我们还需要注意一些剪枝和优化的技巧,比如在IP地址问题中,我们需要对于一些不符合IP地址要求的字符串进行剪枝,以减少递归次数。