代码随想录算法训练营第31天|回溯算法part04| 93.复原IP地址、78.子集、90.子集II
93.复原IP地址
自己做
思路:
分割问题->需要判断子串是否符合条件 并且 组合只能为4
条件为:
- 在0到255之间
int(sub_str) not in range(0, 256)
- 不能有前导0
sub_str.startswith("0") and sub_str != "0"
- 不能为空
len(sub_str) == 0
- 组合数必须为4
if startIndex == len(s) and len(self.path) == 4:
print(self.path)
ip_addr = '.'.join(self.path)
self.result.append(ip_addr)
return
代码:
python
class Solution(object):
def __init__(self):
self.result = []
self.path = []
def restoreIpAddresses(self, s):
"""
:type s: str
:rtype: List[str]
"""
# 思路:
'''
分割问题:
'''
if len(s) < 4 or not s.isdigit():
return []
self.backtracking(s, 0)
return self.result
def backtracking(self, s, startIndex): # 1.
# 2.
if startIndex == len(s) and len(self.path) == 4:
print(self.path)
ip_addr = '.'.join(self.path)
self.result.append(ip_addr)
return
# 3.
for index in range(len(s)):
sub_str = s[startIndex: index+1]
if not self.isSuitable(sub_str):
continue
self.path.append(sub_str)
self.backtracking(s, index+1)
self.path.pop()
return
def isSuitable(self, sub_str):
if len(sub_str) == 0 or int(sub_str) not in range(0, 256) or (sub_str.startswith("0") and sub_str != "0"):
return False
return True
代码随想录
思路:
其实只要意识到这是切割问题,切割问题就可以使用回溯搜索法把所有可能性搜出来,和刚做过的131.分割回文串 (opens new window)就十分类似了。
代码:
python
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:
results.append('.'.join(path))
return
if len(path) > 4: # 剪枝
return
for i in range(index, min(index + 3, len(s))):
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
78.子集
自己做
思路:
刚开始没什么思路
通过画树,知道,结果集中包含的是树中的所有结点加上空结点的集合
也就是说self.path每一次都要加到self.result中,并不是到到叶子结点了才加到self.result中
所以把self.result.append放到了self.path.append的后面
别忘了还有一个空集
代码:
python
class Solution(object):
def __init__(self):
self.result = [[]]
self.path = []
def subsets(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
if len(nums) == 0:
return []
self.backtracking(nums, 0)
return self.result
def backtracking(self, nums, startIndex):
print("startIndex = ", startIndex)
if startIndex >= len(nums):
return
for index in range(startIndex, len(nums)):
self.path.append(nums[index])
print(self.path)
self.result.append(list(self.path))
self.backtracking(nums, index+1)
self.path.pop()
return
代码随想录
思路:
如果把 子集问题、组合问题、分割问题都抽象为一棵树的话,那么组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点!
其实子集也是一种组合问题,因为它的集合是无序的,子集{1,2} 和 子集{2,1}是一样的。
那么既然是无序,取过的元素不会重复取,写回溯算法的时候,for就要从startIndex开始,而不是从0开始!
有同学问了,什么时候for可以从0开始呢?
求排列问题的时候,就要从0开始,因为集合是有序的,{1, 2} 和{2, 1}是两个集合,排列问题我们后续的文章就会讲到的。
以示例中nums = [1,2,3]为例把求子集抽象为树型结构,如下:
从图中红线部分,可以看出遍历这个树的时候,把所有节点都记录下来,就是要求的子集集合。
回溯三部曲
- 递归函数参数
def backtracking(self, nums, startIndex):
- 递归终止条件
if (startIndex >= nums.size()) {
return;
}
其实可以不需要加终止条件,因为startIndex >= nums.size(),本层for循环本来也结束了。
- 单层搜索逻辑
求取子集问题,不需要任何剪枝!因为子集就是要遍历整棵树。
for index in range(startIndex, len(nums)):
self.path.append(nums[index])
self.backtracking(nums, index+1)
self.path.pop()
return
代码:
python
class Solution:
def subsets(self, nums):
result = []
path = []
self.backtracking(nums, 0, path, result)
return result
def backtracking(self, nums, startIndex, path, result):
result.append(path[:]) # 收集子集,要放在终止添加的上面,否则会漏掉自己
# if startIndex >= len(nums): # 终止条件可以不加
# return
for i in range(startIndex, len(nums)):
path.append(nums[i])
self.backtracking(nums, i + 1, path, result)
path.pop()
90. 子集 II
自己做
思路:
-
数组中可能含有重复元素->先排序->通过画树得知,遍历树的过程中横向不可以同时出现,纵向可以同时出现(画图就知道了)
-
如何判断是纵向出现过还是横向出现过呢?
横向:标记为为0,且nums[index] == nums[index-1]
纵向:标记为为1,且nums[index] == nums[index-1]
代码:
python
class Solution(object):
def __init__(self):
self.result = []
self.path = []
def subsetsWithDup(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
'''
思路:
1. 数组中可能含有重复元素->先排序->通过画树得知,遍历树的过程中横向不可以同时出现,纵向可以同时出现(画图就知道了)
2. 如何判断是纵向出现过还是横向出现过呢?
横向:标记为为0,且nums[index] == nums[index-1]
纵向:标记为为1,且nums[index] == nums[index-1]
'''
nums_flag = [0] * len(nums)
nums = sorted(nums)
self.backtracking(nums, 0, nums_flag)
return self.result
def backtracking(self, nums, startIndex, nums_flag):
self.result.append(list(self.path))
if startIndex >= len(nums):
return
for index in range(startIndex, len(nums)):
nums_flag[index] = 1
self.path.append(nums[index])
if index > 0 and nums[index] == nums[index-1] and nums_flag[index-1] == 0:
nums_flag[index] = 0
self.path.pop()
continue
self.backtracking(nums, index+1, nums_flag)
self.path.pop()
nums_flag[index] = 0
return