代码随想录算法训练营第三十天|93.复原IP地址、78.子集、90.子集II

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时,一定是回溯后的,也就是在横向的过程中的,这也就进入了去重的逻辑

  • 20
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值