算法学习 day24 回溯算法03

55 阅读4分钟

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"]

示例 2:

输入: s = "0000"
输出: ["0.0.0.0"]

示例 3:

输入: s = "101023"
输出: ["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"]

提示:

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

思路:

回溯函数定义: 需要start参数避免重复枚举
终止条件:track包含四段数字,然后start迭代到末尾
单层搜索:每次判断从start到i的子串是否合法,合法就递归,不合法就break

有效分割子串的条件:
1 首位不能为0(单个0除外)
2 非负整数,不能超过255
class Solution:
    def restoreIpAddresses(self, s: str) -> List[str]:
        self.result = []
        self.track = []
        self.backtrack(s,0)

        return self.result
    
    def backtrack(self,s:int, start:int)->None:
        # 终止条件 有四段, 并且start迭代到末尾
        if len(self.track) == 4 and start == len(s):
        # if len(self.track) == 4:
            # self.track += s[start:]
            self.result.append(".".join(self.track))
            return 

        # 单层搜索
        for i in range(start, min(start+3,len(s))):
            if self.isValid(s, start, i):
                sub = s[start: i+1]
                self.track.append(sub)
                self.backtrack(s,i+1)
                self.track.pop()
            else:
                break
    
                
    def isValid(self, s:str, start:int, end:int)->bool:
        # 判断子串是否合法,数字
        if start > end:
            return False

        # 非0开头
        if start!= end and s[start] == "0":
            return False
        
        # 判断非负整数,0-255
        total = 0
        for i in range(start, end+1):
            num = s[i]
            if not num.isdigit():
                return False
            
            total = total * 10 + int(num)
            if total > 255:
                return False

        return True

78. 子集

文章讲解

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

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

示例 1:

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

示例 2:

输入: nums = [0]
输出: [[],[0]]

提示:

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

思路:

回溯:
组合问题需要获取叶子节点的路径记录值,而子集问题需要获取所有路径的取值;
并且子集和组合一样不能有重复元素,需要用start来避免重复。

时间复杂度: O(n * 2^n)
空间复杂度: O(n)
class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        self.result = []
        self.track = []
        self.backtrack(nums,0)
        return self.result

    def backtrack(self, nums: List[int], start:int) -> List[List[int]]:
        # 记录树的所有节点
        self.result.append(self.track[:])

        for i in range(start, len(nums)):
            # 选择
            self.track.append(nums[i])
            self.backtrack(nums, i+1)
            # 撤销选择
            self.track.pop()


90. 子集 II

文章讲解

题目:给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的 

子集

(幂集)。

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

示例 1:

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

示例 2:

输入: nums = [0]
输出: [[],[0]]

提示:

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

思路:

子集问题需要记录回溯树每个节点的值,使用start来避免重复,含重复元素需要用used数组去重

时间复杂度: O(n * 2^n)
空间复杂度: O(n)

class Solution:
    def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
        self.result = []
        self.track = []
        self.used = [False] * len(nums)

        nums.sort() # 元素去重需要排序
        self.backtrack(nums, 0)
        return self.result

    
    def backtrack(self, nums: List[int], start:int) -> None:
        # 记录每一个节点值
        self.result.append(self.track[:])

        # 横向遍历树层
        for i in range(start, len(nums)):
            # 跳过树层重复的元素
            # used[i-1] == False表示同一树层nums[i-1]使用过,需要跳过 (数层是不同的集合不能出现相同的答案,如[[1,2],[1,2]],需要跳过)
            # used[i-1] == True表示同一树枝nums[i-1](树枝指纵向的遍历,used设为true了,[1,2,2] 同一个集合里可以出现重复元素)       
            # 跳过同一树层重复的元素         
            if i > 0 and nums[i] == nums[i-1] and not self.used[i-1]:
                continue
            # 选择
            self.track.append(nums[i])
            self.used[i] = True

            # 纵向遍历树枝
            self.backtrack(nums,i+1)
          
            # 撤销选择
            self.track.pop()
            self.used[i] = False

方法2:使用set去重

class Solution:
    def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
        self.result = []
        self.track = []

        nums.sort()
        self.backtrack(nums, 0)
        return self.result

    
    def backtrack(self, nums: List[int], start:int) -> None:
        # 记录每一个节点值
        self.result.append(self.track[:])
        seen = set()
        # 横向遍历树层
        for i in range(start, len(nums)):
            # 跳过树层重复的元素

            if nums[i] in seen:
                continue
            
            seen.add(nums[i])
            # 选择
            self.track.append(nums[i])

            # 纵向遍历树枝
            self.backtrack(nums,i+1)
          
            # 撤销选择
            self.track.pop()