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

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

93.复原IP地址

93.复原IP地址

自己做

思路:

分割问题->需要判断子串是否符合条件 并且 组合只能为4

条件为:

  1. 在0到255之间

int(sub_str) not in range(0, 256)

  1. 不能有前导0

sub_str.startswith("0") and sub_str != "0"

  1. 不能为空

len(sub_str) == 0

  1. 组合数必须为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

90. 子集 II

自己做

思路:

  1. 数组中可能含有重复元素->先排序->通过画树得知,遍历树的过程中横向不可以同时出现,纵向可以同时出现(画图就知道了)

  2. 如何判断是纵向出现过还是横向出现过呢?

横向:标记为为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


        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值