Leetcode刷题笔记28:回溯4(93. 复原 IP 地址、78. 子集、90. 子集 II)

125 阅读2分钟

导语

leetcode刷题笔记记录,本篇博客记录回溯部分的题目,主要题目包括:

Leetcode 93. 复原 IP 地址

题目描述

有效 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 中的任何数字。你可以按 任何 顺序返回答案。

示例 1:

输入: s = "25525511135"
输出: ["255.255.11.135","255.255.111.35"]

提示:

  • 1 <= s.length <= 20
  • s 仅由数字组成

解法

仍然使用回溯法解决,为此确定三要素:

  1. 递归的参数:这里由于IP地址实际上就是4段0-255的数字,所以递归的深度应该为4层,需要有一个point_sum参数控制层数,同时仍然需要一个start_index来控制不使用前面的元素。
  2. 终止条件:当point_sum为4时,需要判断是否完全处理了全部的字符串,如果处理完成,则将结果按字符串输出格式拼接后放入result中;
  3. 单层逻辑:与分割回文子序列类似,子串使用s[start_index: i+1]表示,在循环时,只需要在子串有效时添加到path中,继续下一次递归即可。

示意图(参考[代码随想录] image.png

这里可以使用一个剪枝操作就是每次最多传入长度为3的子串(因为ip地址对应最多3个字符)。具体代码如下:

class Solution:
    def __init__(self):
        self.path = []
        self.result = []

    def restoreIpAddresses(self, s: str) -> List[str]:
        self.back_tracking(s, 0, 0)

        return self.result

    def back_tracking(self, s, start_index, point_sum):
        if point_sum == 4:  # 如果已经分割成4段
            if start_index == len(s):  # 并且字符串已经被完全使用
                self.result.append(".".join(self.path[:]))
            return
            
        for i in range(start_index, min(start_index + 3, len(s))):
            if self.is_valid(s[start_index: i+1]):
                self.path.append(s[start_index: i+1])
                point_sum += 1
                self.back_tracking(s, i+1, point_sum)
                point_sum -= 1
                self.path.pop()

    def is_valid(self, s):
        if not s:
            return False
        if s[0] == '0' and len(s) > 1:  # 0开头的数字不合法
            return False
        num = int(s)
        return 0 <= num <= 255

同时,也可以缩减一层递归深度,即我们在递归到第3层后进行返回时判断末尾的子串是否有效,如果可以,则直接返回,这样可以减少一层深度,提高效率,但需要在返回判断逻辑里进行回溯,格式与之前不太类似,参考代码如下:

class Solution:
    def __init__(self):
        self.path = []
        self.result = []

    def restoreIpAddresses(self, s: str) -> List[str]:
        self.back_tracking(s, 0, 0)

        return self.result

    def back_tracking(self, s, start_index, point_sum):
        if point_sum == 3:
            if self.is_valid(s[start_index:]):
                self.path.append(s[start_index:])
                self.result.append(".".join(self.path[:]))
                self.path.pop()
            return
        for i in range(start_index, min(start_index + 3, len(s))):
            if self.is_valid(s[start_index: i+1]):
                self.path.append(s[start_index: i+1])
                point_sum += 1
                self.back_tracking(s, i+1, point_sum)
                point_sum -= 1
                self.path.pop()

    def is_valid(self, s):
        if not s:
            return False
        if s[0] == '0' and len(s) > 1:  # 0开头的数字不合法
            return False
        num = int(s)
        return 0 <= num <= 255

Leetcode 78 子集

题目描述

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例 1:

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

提示:

  • 1 <= nums.length <= 10
  • -10 <= nums[i] <= 10
  • nums 中的所有元素 互不相同

解法

子集问题与组合问题稍有不同,组合问题主要在叶子节点上收获结果,而子集问题则是在任何一个递归调用时都要收获结果。

这道题实际上只需要将每个递归的步骤下的节点全部都返回到结果即可,这里还是使用一个start_index用于减少重复。

class Solution:
    def __init__(self):
        self.path = []
        self.result = []

    def subsets(self, nums: List[int]) -> List[List[int]]:
        self.back_tracking(nums, 0)

        return self.result

    def back_tracking(self, nums, start_index):
        self.result.append(self.path[:])
        if start_index >= len(nums):
            return
        for i in range(start_index, len(nums)):
            self.path.append(nums[i])
            self.back_tracking(nums, i+1)
            self.path.pop()

        return

Leetcode 90 子集 II

题目描述

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

示例 1:

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

提示:

  • 1 <= nums.length <= 10
  • -10 <= nums[i] <= 10

解法

相比于上一道题目,本题的增加了一个去重的操作,这和组合II的题目非常类似,我们还是采用一个used数组用于保存相同值元素的使用情况。

求子集的问题一般并不需要设置返回,因为遍历到叶子节点后会自动结束所有逻辑,所以也可以不设置返回判断。所以,整个代码的主要改动之处在于循环中进行一个判断:

            if i > 0 and nums[i] == nums[i-1] and used[i-1]==0:
                continue

完整的代码如下:

class Solution:
    def __init__(self):
        self.path = []
        self.result = []

    def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
        used = [False] * len(nums)
        nums.sort()
        self.back_tracking(nums, 0, used)

        return self.result


    def back_tracking(self, nums, start_index, used):
        self.result.append(self.path[:])
        if start_index >= len(nums):
            return
        for i in range(start_index, len(nums)):
            if i > 0 and nums[i] == nums[i-1] and used[i-1]==0:
                continue
            self.path.append(nums[i])
            used[i] = True
            self.back_tracking(nums, i+1, used)
            used[i] = False
            self.path.pop()

        return

参考

  1. 代码随想录, programmercarl.com/