93.复原IP地址
文档讲解:代码随想录
题目链接:. - 力扣(LeetCode)
这个题与上一个题代码随想录一样,都是切割问题
回溯三部曲
- 递归参数
在131.分割回文串 (opens new window)中我们就提到切割问题类似组合问题。
startIndex一定是需要的,因为不能重复分割,记录下一层递归分割的起始位置。
本题我们还需要一个变量pointNum,记录添加逗点的数量。
- 递归终止条件
终止条件和131.分割回文串 (opens new window)情况就不同了,本题明确要求只会分成4段,所以不能用切割线切到最后作为终止条件,而是分割的段数作为终止条件。
pointNum表示逗点数量,pointNum为3说明字符串分成了4段了。
然后验证一下第四段是否合法,如果合法就加入到结果集里
- 单层搜索的逻辑
在131.分割回文串 (opens new window)中已经讲过在循环遍历中如何截取子串。
在for (int i = startIndex; i < s.size(); i++)
循环中 [startIndex, i] 这个区间就是截取的子串,需要判断这个子串是否合法。
如果合法就在字符串后面加上符号.
表示已经分割。
如果不合法就结束本层循环,如图中剪掉的分支。
难点
这个题还是比较难的,细节很多,比如判断是否合法,记录. 的个数以及判断相应的字符是否合法,对字符串的修改等
78.子集
文档讲解:代码随想录
题目链接:. - 力扣(LeetCode)
这个题和求子集问题和77.组合 (opens new window)和131.分割回文串 (opens new window)又不一样了。
如果把 子集问题、组合问题、分割问题都抽象为一棵树的话,那么组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点!
其实子集也是一种组合问题,因为它的集合是无序的,子集{1,2} 和 子集{2,1}是一样的。
那么既然是无序,取过的元素不会重复取,写回溯算法的时候,for就要从startIndex开始,而不是从0开始!
以示例中nums = [1,2,3]为例把求子集抽象为树型结构,如下:
回溯三部曲
- 递归函数参数
全局变量数组path为子集收集元素,二维数组result存放子集组合。(也可以放到递归函数参数里)
递归函数参数在上面讲到了,需要startIndex。
递归终止条件
从图中可以看出:
剩余集合为空的时候,就是叶子节点。
那么什么时候剩余集合为空呢?
就是startIndex已经大于数组的长度了,就终止了,因为没有元素可取了。其实可以不需要加终止条件,因为startIndex >= nums.size(),本层for循环本来也结束了。
- 单层搜索逻辑
求取子集问题,不需要任何剪枝!因为子集就是要遍历整棵树。
from typing import List
import copy
class Solution:
def __init__(self):
self.path = [] # 用于存储当前的子集路径
self.result = [] # 用于存储所有子集的结果
def subsets(self, nums: List[int]) -> List[List[int]]:
def backtracking(nums, start_index):
# 将当前的子集路径添加到结果集中
self.result.append(copy.deepcopy(self.path))
# 遍历所有可能的后续元素
for i in range(start_index, len(nums)):
# 将当前元素添加到路径中
self.path.append(nums[i])
# 递归调用,生成从当前元素之后的所有子集
backtracking(nums, i + 1)
# 回溯,将当前元素从路径中移除
self.path.pop()
# 从索引0开始,进行回溯搜索
backtracking(nums, 0)
# 返回所有生成的子集
return self.result
90.子集II
文档讲解:代码随想录
题目链接:. - 力扣(LeetCode)
这个题算是78.子集 (opens new window)和40.组合总和II的组合,就是要保留每个节点的值,并且要做去重
class Solution:
def __init__(self):
self.path = []
self.result = []
def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
def backtracking(nums,start_index):
self.result.append(copy.deepcopy(self.path))
for i in range(start_index,len(nums)):
if i > start_index:
if nums[i]==nums[i-1]:
continue
self.path.append(nums[i])
backtracking(nums,i+1)
self.path.pop()
nums.sort()#不能直接写到函数里作为参数,该函数的返回值是None
backtracking(nums,0)
return self.result
至于为什么判断i > start_index可以达到去重的目的在组合总和2的笔记中并没有写,因为当时还不太懂,今天明白了一点,这个操作保证了在纵向递归的过程中不会进入去重逻辑的。也就是在纵向的过程中是可以取到重复的元素的,在纵向递归的过程中,i是不会等于start_index的,是一路向下的,当i取到等于start_index时,一定是回溯后的,也就是在横向的过程中的,这也就进入了去重的逻辑