代码随想录训练营 Day24打卡 回溯算法part03
一、 力扣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 中的任何数字。你可以按 任何 顺序返回答案。
示例 :
输入:s = “25525511135”
输出:[“255.255.11.135”,“255.255.111.35”]
切割问题可以抽象为树型结构,如图:
实现思路
-
初始调用:
调用 restoreIpAddresses 方法时,初始化结果列表并调用 backtracking 方法。
-
回溯函数 backtracking:
当逗点数量为 3 时,判断最后一段子字符串是否合法,如果合法则添加到结果列表中。
循环遍历字符串,判断当前子字符串是否合法,若合法则递归调用回溯函数。
不合法时,停止当前循环。 -
验证函数 is_valid:
判断子字符串是否符合 IP 地址段的规则:不能有前导零、不能大于 255、必须是数字字符。
版本一 回溯法
from typing import List
class Solution:
def restoreIpAddresses(self, s: str) -> List[str]:
result = []
# 初始调用回溯函数,开始索引、逗点数量、当前字符串、结果列表
self.backtracking(s, 0, 0, "", result)
return result
def backtracking(self, s, start_index, point_num, current, result):
if point_num == 3: # 逗点数量为3时,分隔结束
if self.is_valid(s, start_index, len(s) - 1): # 判断第四段子字符串是否合法
current += s[start_index:] # 添加最后一段子字符串
result.append(current)
return
for i in range(start_index, len(s)):
if self.is_valid(s, start_index, i): # 判断 [start_index, i] 这个区间的子串是否合法
sub = s[start_index:i + 1] # 获取当前段
# 递归调用回溯函数,传入新的开始索引、增加的逗点数量、当前字符串、结果列表
self.backtracking(s, i + 1, point_num + 1, current + sub + '.', result)
else:
break # 不合法则停止循环
def is_valid(self, s, start, end):
if start > end:
return False
if s[start] == '0' and start != end: # 0开头的数字不合法
return False
num = 0
for i in range(start, end + 1):
if not s[i].isdigit(): # 遇到非数字字符不合法
return False
num = num * 10 + int(s[i]) # 累积数字
if num > 255: # 如果大于255了不合法
return False
return True
版本二 回溯法
实现思路
-
初始调用:
调用 restoreIpAddresses 方法时,初始化结果列表并调用 backtracking 方法。
-
回溯函数 backtracking:
当遍历完字符串且路径长度为 4 时,将路径加入结果列表。
路径长度大于 4 时,停止回溯(剪枝)。
循环遍历字符串,判断当前子字符串是否合法,若合法则递归调用回溯函数并回溯路径。 -
验证函数 is_valid:
判断子字符串是否符合 IP 地址段的规则:不能有前导零、必须在 0 到 255 之间。
from typing import List
class Solution:
def restoreIpAddresses(self, s: str) -> List[str]:
results = []
# 初始调用回溯函数,开始索引、路径、结果列表
self.backtracking(s, 0, [], results)
return results
def backtracking(self, s, index, path, results):
if index == len(s) and len(path) == 4: # 完全遍历字符串且路径长度为4(四段IP)
results.append('.'.join(path)) # 加入结果列表
return
if len(path) > 4: # 剪枝:路径长度大于4时停止
return
for i in range(index, min(index + 3, len(s))): # 每段最长3个字符
if self.is_valid(s, index, i):
sub = s[index:i+1] # 获取当前段
path.append(sub) # 将当前段加入路径
# 递归调用回溯函数,传入新的开始索引、路径、结果列表
self.backtracking(s, i+1, path, results)
path.pop() # 回溯:移除最后一个元素
def is_valid(self, s, start, end):
if start > end:
return False
if s[start] == '0' and start != end: # 0开头的数字不合法
return False
num = int(s[start:end+1]) # 将子字符串转为整数
return 0 <= num <= 255 # 数字必须在0到255之间
力扣题目链接
题目文章讲解
题目视频讲解
二、 力扣78. 子集
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 :
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
以示例中nums = [1,2,3]为例把求子集抽象为树型结构,如下:
从图中红线部分,可以看出遍历这个树的时候,把所有节点都记录下来,就是要求的子集集合。
实现思路
-
初始调用:
调用 restoreIpAddresses 方法时,初始化结果列表并调用 backtracking 方法。
-
回溯函数 backtracking:
当遍历完字符串且路径长度为 4 时,将路径加入结果列表。
路径长度大于 4 时,停止回溯(剪枝)。
循环遍历字符串,判断当前子字符串是否合法,若合法则递归调用回溯函数并回溯路径。 -
验证函数 is_valid:
判断子字符串是否符合 IP 地址段的规则:不能有前导零、必须在 0 到 255 之间。
代码实现
from typing import List
class Solution:
def restoreIpAddresses(self, s: str) -> List[str]:
results = []
# 初始调用回溯函数,开始索引、路径、结果列表
self.backtracking(s, 0, [], results)
return results
def backtracking(self, s, index, path, results):
if index == len(s) and len(path) == 4: # 完全遍历字符串且路径长度为4(四段IP)
results.append('.'.join(path)) # 加入结果列表
return
if len(path) > 4: # 剪枝:路径长度大于4时停止
return
for i in range(index, min(index + 3, len(s))): # 每段最长3个字符
if self.is_valid(s, index, i):
sub = s[index:i+1] # 获取当前段
path.append(sub) # 将当前段加入路径
# 递归调用回溯函数,传入新的开始索引、路径、结果列表
self.backtracking(s, i+1, path, results)
path.pop() # 回溯:移除最后一个元素
def is_valid(self, s, start, end):
if start > end:
return False
if s[start] == '0' and start != end: # 0开头的数字不合法
return False
num = int(s[start:end+1]) # 将子字符串转为整数
return 0 <= num <= 255 # 数字必须在0到255之间
力扣题目链接
题目文章讲解
题目视频讲解
三、 力扣90. 子集II
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的 子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
示例 :
输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]
做本题之前一定要先做78.子集 。
这道题目和78.子集区别就是集合里有重复元素了,而且求取的子集要去重。
那么关于回溯算法中的去重问题,在40.组合总和II (opens new window)中已经详细讲解过了,和本题是一个套路。
用示例中的[1, 2, 2] 来举例,如图所示: (注意去重需要先对集合排序)
从图中可以看出,同一树层上重复取2 就要过滤掉,同一树枝上就可以重复取2,因为同一树枝上元素的集合才是唯一子集!
实现思路
-
初始化:
result:用于存储所有子集的结果列表。
path:用于存储当前探索的路径(子集)。
used:用于标记数组中的元素是否被使用过,以避免重复。
将数组 nums 进行排序,方便后续去重操作。
调用 backtracking 方法,开始回溯算法。 -
回溯函数 backtracking:
每次进入递归函数,都将当前路径 path 添加到结果列表 result 中,表示一个子集。
循环遍历数组 nums,从 startIndex 开始,以避免重复子集。
检查当前元素是否与前一个元素相同,且前一个元素未被使用过,以避免在同一树层上出现重复的元素组合。
对于每个元素:
将当前元素添加到路径 path 中。
标记当前元素为已使用 used[i] = True。
递归调用 backtracking,探索以当前元素为起点的所有子集。
回溯操作,取消当前元素的使用标记 used[i] = False,并从路径中移除最后一个元素 path.pop()。
代码实现
class Solution:
def subsetsWithDup(self, nums):
result = [] # 用于存放所有子集的结果列表
path = [] # 当前探索的路径(子集)
used = [False] * len(nums) # 用于标记元素是否被使用过
nums.sort() # 去重需要排序
self.backtracking(nums, 0, used, path, result) # 调用回溯函数
return result # 返回最终的结果列表
def backtracking(self, nums, startIndex, used, path, result):
result.append(path[:]) # 每次进入递归函数,都将当前路径添加到结果中,表示一个子集
for i in range(startIndex, len(nums)):
# 跳过重复的元素
# used[i - 1] == True,说明同一树枝 nums[i - 1] 使用过
# used[i - 1] == False,说明同一树层 nums[i - 1] 使用过
# 而我们要对同一树层使用过的元素进行跳过
if i > 0 and nums[i] == nums[i - 1] and not used[i - 1]:
continue
path.append(nums[i]) # 将当前元素添加到路径中
used[i] = True # 标记当前元素为使用过
# 递归调用,探索以当前元素为起点的所有子集
self.backtracking(nums, i + 1, used, path, result)
used[i] = False # 回溯,取消当前元素的使用标记
path.pop() # 回溯,移除路径中的最后一个元素
# 示例
sol = Solution()
print(sol.subsetsWithDup([1, 2, 2]))
力扣题目链接
题目文章讲解
题目视频讲解