算法训练营第二十一天 | 回溯算法(三)

文章目录

  • 一、Leetcode 93.复原IP地址
  • 二、Leetcode 78.子集
  • 三、Leetcode 90.子集Ⅱ


一、Leetcode 93.复原IP地址

有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 ‘.’ 分隔。

例如:"0.1.2.201""192.168.1.1" 是 有效 IP 地址,但是 "0.011.255.245""192.168.1.312""[email protected]" 是 无效 IP 地址。
给定一个只包含数字的字符串 s ,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s 中插入 '.' 来形成。你不能重新排序或删除 s 中的任何数字。你可以按任何顺序返回答案。

示例:

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

引用:

原文链接:https://programmercarl.com/0093.%E5%A4%8D%E5%8E%9FIP%E5%9C%B0%E5%9D%80.html
题目链接:https://leetcode.cn/problems/restore-ip-addresses/description/
视频讲解:https://www.bilibili.com/video/BV1XP4y1U73i/

和切割回文串类似,但是多了一些判断条件。

我们需要一个判断IP是否有效的函数,这个函数比较简单,不做过多解释。

递归三部曲:

主要参数和返回值:

  1. 无返回值
  2. 需要一个循环的起始位置 'startIndex

终止条件:
我们的IP地址是由4段数字组成,当 len(self.path) == 4 时,说明我们已经得到了完整的IP地址。然后我们来判断其实位置是否等于字符串 s 的长度,来判断我们所有的原材料是否都用完(题目要求不能有剩余)。

单层递归的逻辑:
和切割字符串一样

代码:

class Solution:
    def restoreIpAddresses(self, s: str) -> List[str]:
        self.result = []
        self.path = []
        self.backtracking(s, 0)
        return self.result

    def backtracking(self, s, startIndex):
        if len(self.path) == 4:
            if startIndex == len(s):
                self.result.append('.'.join(self.path))
            return

        for i in range(startIndex, min(startIndex + 3, len(s))):
            cur_s = s[startIndex:i + 1]
            if self.is_legal(cur_s):
                self.path.append(cur_s)
                self.backtracking(s, i + 1)
                self.path.pop()

    def is_legal(self, num):
        if len(num) > 1 and num[0] == '0':
            return False
        return 0 <= int(num) <= 255

二、Leetcode 78.子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例:

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

引用:

原文链接:https://programmercarl.com/0078.%E5%AD%90%E9%9B%86.html
题目链接:https://leetcode.cn/problems/subsets/description/
视频讲解:https://www.bilibili.com/video/BV1U84y1q7Ci/

和组合问题差不多。

组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点!

这里我们其实可以不需要终止条件了,其实可以不需要加终止条件,因为startIndex >= nums.size(),本层for循环本来也结束了。

因为要收集的是所有结点而不是叶子结点,所以保存结果的代码不能放在终止条件里。

代码:

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        result = []
        path = []

        def backtracking(start_index):
            # 每次进入回溯函数时,当前的 path 都是一个子集
            result.append(path[:])
            for i in range(start_index, len(nums)):
                # 做出选择,将当前元素加入 path
                path.append(nums[i])
                # 递归调用,继续处理下一个元素
                backtracking(i + 1)
                # 撤销选择,回溯到上一步
                path.pop()

        backtracking(0)
        return result

三、Leetcode 90.子集Ⅱ

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

解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

示例:

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

引用:

原文链接:https://programmercarl.com/0090.%E5%AD%90%E9%9B%86II.html
题目链接:https://leetcode.cn/problems/subsets-ii/description/
视频讲解:https://www.bilibili.com/video/BV1vm4y1F71J/

LeetCode 第 90 题 “子集 II” 与第 78 题 “子集” 的区别在于,给定的数组中可能包含重复元素,要求返回的子集不能有重复。

为了避免生成重复的子集,我们可以先对数组进行排序,这样相同的元素会相邻。在回溯过程中,如果当前元素和前一个元素相同,并且不是在处理该元素的第一个出现位置(即 i > start_index),就跳过这个元素,以此来避免生成重复的子集。

这种去重实际上是 数层去重,因为在同一层中,不能出现相同的元素,但是在同一个 树枝上,可以出现。

代码:

class Solution:
    def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
        result = []
        path = []
        # 对数组进行排序,让相同元素相邻
        nums.sort()

        def backtracking(start_index):
            # 每次进入回溯函数时,当前的 path 都是一个子集
            result.append(path[:])
            for i in range(start_index, len(nums)):
                # 跳过重复元素
                if i > start_index and nums[i] == nums[i - 1]:
                    continue
                # 做出选择,将当前元素加入 path
                path.append(nums[i])
                # 递归调用,继续处理下一个元素
                backtracking(i + 1)
                # 撤销选择,回溯到上一步
                path.pop()

        backtracking(0)
        return result

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