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

LeetCode 93.复原IP地址:

文章链接
题目链接:93.复原IP地址

思路:

对应树如下,红色部分为划分错误的节点
在这里插入图片描述
① 传入参数:s,sIP记录路径,left为当前s的开始端点,right为s的结束端点(双闭区间),count为IP地址中还需要的“.”的数量(一共四个)
② 边界条件:
1)待分配的字符串为空,且count = 0,将sIP加入result中
2)待分配字符串为空,或count=0。表明要么字符串为空时,IP没有分配完,要么字符串不为空,IP地址已经有4个整数
3)当前待分配的字符串的长度超出了还需要的IP地址的长度(不包含".")
PS:这个边界条件是字符串为空,也可以边界条件为IP地址还剩下一个整数没有分配
③ 遍历:划分当前IP地址的整数的结束范围,同时判断划分的整数是否合法(非单个字符的前导0,包含非数字,数字范围超限)
同时还可以增加剪枝,因为IP地址中的整数最大位数为3,因此 i 在[left, min(left + 2, right)]中

"""
边界为空字符串和count = 0
"""
class Solution:
    def restoreIpAddresses(self, s: str) -> List[str]:
        if len(s) < 4:
            return []
        self.result = []
        self.backtracking(s, "", 0, len(s) - 1, 4)
        return self.result
        
    def isvalid(self, s, left, right):  # 双闭区间
        if left > right:
            return False
        if s[left] == "0" and left != right:   # 非单个字符含前导0不合法
            return False
        num = 0
        for si in s[left:right + 1]:    # 包含非数字
            if not si.isdigit():
                return False
            num = num * 10 + int(si)
            if num > 255:
                return False
        
        return True
    def backtracking(self, s, sIP, left, right, count):
        if left > right and count == 0:
            # print("append: " + sIP)
            self.result.append(sIP[:-1])    # sIP最后多了一个"."
            return
        elif left > right or count == 0:
            # print("left > right or count == 0")
            return
        elif right - left + 1 > count * 3:  # 剩余未分割字符串的长度超出了需要的长度
            # print("right - left + 1 > count * 3")
            return
        # print("backtracking start, left is: " + str(left) + ": " + s[left])
        maxi = min(left + 3, right + 1) # 容易忘记, 有效IP地址的单个整数位数最多为3
        for i in range(left, maxi): 
            if self.isvalid(s, left, i):
                # print("sIP append: " + s[left:i + 1])
                sIP = sIP + s[left: i + 1] + "."
                # print("sIP: " + sIP)
                self.backtracking(s, sIP, i + 1, right, count - 1)
                sIP = sIP[:left - i - 2]
                # print("sIP back: " + sIP)

"""
边界条件为IP地址还剩一个整数
"""
class Solution:
    def restoreIpAddresses(self, s: str) -> List[str]:
        if len(s) < 4:
            return []
        self.result = []
        self.backtracking(s, 0, len(s) - 1, "", 4)
        return self.result
        
    def isValid(self, s, left, right):  # [left, right]双闭区间
        if left > right:
            return False
        if s[left] == "0" and left != right:   # 非单个字符的前导0
            return False
        num = 0
        for si in s[left:right + 1]:
            if not si.isdigit():    # 包含非数字
                return False
            num = num * 10 + int(si)
            if num > 255:   # 超出范围
                return False
        return True
    def backtracking(self, s, left, right, sIP, count):
        if count == 1:  # IP地址还剩一个整数
            if self.isValid(s, left, right):
                sIP += s[left:right + 1]
                self.result.append(sIP)
            return
        if right - left + 1 > count * 3:    # 剪枝
            return 
        for i in range(left, min(left + 3, right)):	# 整数最大为三位
            if self.isValid(s, left, i):
                sub = s[left:i + 1] + "."
                self.backtracking(s, i + 1, right, sIP + sub, count - 1)     

感悟:

需要注意的细节:① 判断是否合法:非单个字符前导0,包含非数字,超限
② 正常遍历时,因为有效IP的单个整数最高为三位,但是可能会出现left + 2 > right的情况,因此范围为[left, min(left + 2, right)],不管边界条件是什么,遍历中 i 的范围都是上面


LeetCode 78.子集:

文章链接
题目链接:78.子集

思路:

子集问题是求解对应树的所有结点,其中结点的值为当前的子集,也即根结点到当前结点的路径,同时因为不能包含重复的子集,所以之前使用过的值在同层结点不再使用,即构造子集从startIndex开始,对应的树如下,
在这里插入图片描述
① 参数:nums,startIndex为构建子节点的集合开始位置,path记录路径,也即当前结点对应的子集
② 边界条件:由上图二叉树可知,边界条件为剩余集合为空,即startIndex >= len(nums)
③ 遍历:正常回溯递归,且不需要剪枝

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        self.result = []
        self.backtracking(nums, 0, [])
        return self.result

    def backtracking(self, nums, startIndex, path):
        self.result.append(path[:]) # 放前面保证叶子节点也加入result中
        if startIndex >= len(nums):
            return
        for i in range(startIndex, len(nums)):
            path.append(nums[i])
            self.backtracking(nums, i + 1, path)    # 不能包含重复子集,递归的startIndex为i + 1
            path.pop()

        

感悟:

子集问题转换为求对应树的所有结点,结点值为根节点到当前结点的路径。


LeetCode 90.子集Ⅱ:

文章链接
题目链接:90.子集Ⅱ

思路:

是前面组合总和Ⅱ与子集的结合。对应的树如下,其中红色部分是需要去重的部分。
首先需要记录树中全部节点的值(根节点到当前节点的路径),然后需要去重,首先需要对集合进行排序,应当是在同一树层进行去重,因此是在for循环中去重。这里采用的一种去重方式为i > startIndex and nums[i] == num[i - 1],跳过同一树层中重复的值。
在这里插入图片描述

① 参数:nums,startIndex开始位置(因为获得的子集不能重复),path记录从根节点到当前节点的路径,也就是当前节点的值
② 边界条件:剩余集合为空,即startIndex >= len(nums)
③ 遍历:一般的回溯遍历

"""
采用i > startIndex and nums[i] == nums[i - 1]去重
"""
class Solution:
    def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
        self.result = []
        nums.sort() # 记得先排序
        self.backtracking(nums, 0, [])
        return self.result

    def backtracking(self, nums, startIndex, path):
        self.result.append(path[:])
        if startIndex >= len(nums):  # 也可以不加,没什么影响
            return
        for i in range(startIndex, len(nums)):
            if i > startIndex and nums[i] == nums[i - 1]:   # 只在当前循环去除重复的
                continue
            path.append(nums[i])
            self.backtracking(nums, i + 1, path)    # 不能包含重复的子集,设置为i + 1
            path.pop()
        
"""
使用set在for循环中去重(去重前集合先排序)
"""
class Solution:
    def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
        self.result = []
        nums.sort() # 记得先排序
        self.backtracking(nums, 0, [])
        return self.result

    def backtracking(self, nums, startIndex, path):
        self.result.append(path[:])
        uset = set()
        if startIndex > len(nums):  # 也可以不加,没什么影响
            return
        for i in range(startIndex, len(nums)):
            if nums[i] in uset:   # 采用set去重
                continue
            uset.add(nums[i])
            path.append(nums[i])
            self.backtracking(nums, i + 1, path)    # 不能包含重复的子集,设置为i + 1
            path.pop()
        
"""
使用used数组去重。
在for循环中,used[i - 1] = True只可能nums[i - 1]为当前节点的前面树枝上的节点,因此为同一树枝上的节点
used[i - 1] = False,nums[startIndex :]中的节点都可以是同一树层上的节点,因此used[i - 1]表明nums[i - 1]与nums[i]是同一树层上的节点
"""
class Solution:
    def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
        self.result = []
        nums.sort() # 记得先排序
        used = [False] * len(nums)
        self.backtracking(nums, 0, [], used)
        return self.result

    def backtracking(self, nums, startIndex, path, used):
        self.result.append(path[:])
        # uset = set()
        if startIndex > len(nums):  # 也可以不加,没什么影响
            return
        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
            #uset.add(nums[i])
            path.append(nums[i])
            used[i] = True
            self.backtracking(nums, i + 1, path, used)    # 不能包含重复的子集,设置为i + 1
            used[i] = False
            path.pop()
        

感悟:

去重前先对集合排序
多种去重方法:used数组,set去重以及i > startIndex去重


学习收获:

组合、切割问题都是求树的叶子节点,子集问题是求树的全部节点。切割问题先构造对应的树,再敲代码会好理解一点;且需要分清楚边界条件为IP地址在前面递归中全部确定且字符串为空,还是字符串中还剩下最后一部分,作为IP的最后一个整数;去重前先排序,三种去重方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值