• 代码随想录 - Day33 - 回溯:切割问题,子集问题


    代码随想录 - Day33 - 回溯:切割问题,子集问题

    131. 分割回文串

    举个例子:["aabac"]
    可以分割为以下三种:
    ["a","a","b","a","c"] ["a","aba","c"] ["aa","b","a","c"]
    画图理解:(mermaid画的图有些线的位置比较奇怪,看仔细一点)
    绿色框内为找到的分割方案,找到后返回.
    灰色框表示非回文串,遇到不是回文串的情况,不再继续向下遍历.
    for循环横向遍历,递归纵向遍历.
    横向遍历遇到不是回文串的情况,则直接跳到下一个(for循环内需要先判断回文串,再进行递归).
    判断是否回文串时,可以用双指针法,也可以直接判断s[start_index: i + 1](正序)和s[start_index: i + 1][::-1](逆序)是否相等.

    截取a
    截取aa
    截取aab
    截取aaba
    截取aabac
    截取a
    截取ab
    截取aba
    截取abac
    截取b
    截取ba
    截取bac
    截取b
    截取ba
    截取bac
    截取c
    截取a
    截取ac
    截取a
    截取ac
    截取c
    截取c
    aab|ac
    aab不是回文
    aaba|c
    aaba不是回文
    aabac|
    aabac不是回文
    剩下为空
    a|ab|ac
    ab不是回文
    a|abac|
    abac不是回文
    aa|ba|c
    ba不是回文
    aa|bac|
    bac不是回文
    a|a|ba|c
    ba不是回文
    a|a|bac|
    bac不是回文
    aa|b|ac|
    ac不是回文
    a|a|b|ac|
    ac不是回文
    a|aba|c|
    切割完毕
    aa|b|a|c|
    切割完毕
    a|a|b|a|c|
    切割完毕
    在aabac中截取
    a|abac
    在abac中截取
    aa|bac
    在bac中截取
    a|a|bac
    在bac中截取
    a|aba|c
    在c中截取
    aa|b|ac
    在ac中截取
    a|a|b|ac
    在ac中截取
    aa|b|a|c
    在c中截取
    a|a|b|a|c
    在c中截取
    class Solution:
        def isPalindrome(self, s, start, end):  # 判断是否回文
            i, j = start, end
            while i < j:
                if s[i] != s[j]:
                    return False
                i += 1
                j -= 1
            return True
                
        def backtracking(self, s, startIndex, result, path):
            # 切割位置与s长度相同时,找到一组分割方案
            if startIndex == len(s):
                result.append(path[:])
                return
            # 单层递归逻辑
            # 每次切割的位置(startIndex)要向后移一个
            for i in range(startIndex, len(s)):
                if self.isPalindrome(s, startIndex, i):
                    path.append(s[startIndex:i + 1])
                    self.backtracking(s, i + 1, result, path)   # 递归
                    path.pop()  # 回溯
    
        def partition(self, s: str) -> List[List[str]]:
            result = []
            self.backtracking(s, 0, result, [])
            return result
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    93. 复原 IP 地址

    举例:s = "10312"
    输出:["1.0.3.12","1.0.31.2","10.3.1.2"]

    截取1<=255
    截取10<=255
    截取103<=255
    截取0<=255
    截取0...
    不能含有前导0
    截取3<=255
    截取31<=255
    截取3<=255
    截取31<=255
    截取312>255
    截取1
    1.0.3.12
    有效IP
    1.0.31.2
    有效IP
    10.3.1.2
    有效IP
    103.12.
    剩余数字不足
    结束
    结束
    10.31.2.
    剩余数字不足
    结束
    1.0.312.
    错误
    在10312中截取
    1.0312
    在0312中截取
    10.312
    在312中截取
    1.0.312
    在312中截取
    10.3.12
    在12中截取

    • 由于有效IP地址是由四个整数构成的,因此这道题的递归次数最多只有四次
      IP地址通过.分割为四段,因此.的数量等于3时,则结束递归
    • 判断每段是否有效:
      • 段位以0为开头的数字不合法 if s[start] == '0' and start != end: return False
      • 段位里有非正整数字符不合法 if not s[i].isdigit(): return False
      • 段位小于0或大于255不合法(本题中不涉及小于0的情况,因此只需要考虑是否大于255的情况)if num > 255: return False
    • 要想构成有效IP地址,s的长度必须在[4,12]之间 if len(s) < 4 or len(s) > 12: return []
    class Solution:
        def isOK(self, s, start, end):
            if start > end:
                return False
            if s[start] == '0' and start != end:
                return False
            num = 0
            for i in range(start, end + 1):
                if not s[i].isdigit():
                    return False
                num = num * 10 + int(s[i])
                if num > 255:
                    return False
            return True
    
        def backtracking(self, s, startIndex, pointNum,  current, result):
            if pointNum == 3:
                if self.isOK(s, startIndex, len(s) - 1):
                    current += s[startIndex:]
                    result.append(current)
                return
            # 单层递归逻辑
            # 每次切割的位置向后移一个
            for i in range(startIndex, len(s)):
                if self.isOK(s, startIndex, i):
                    sub = s[startIndex:i + 1]
                    self.backtracking(s, i + 1, pointNum + 1, current + sub + '.', result)
                else:
                    break
    
        def restoreIpAddresses(self, s: str) -> List[str]:
            if len(s) < 4 or len(s) > 12:   # 直接排除掉不可能构成有效IP的情况
                return []
            result = []
            self.backtracking(s, 0, 0, "", result)
            return result
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    上面那种写法我能理解,但是和我自己的思路还是不太一样。
    下面这种写法就比较符合我的思路。
    str.join(item)str表示字符串(字符),item表示一个成员(注意括号里必须只能有一个成员)
    该函数的作用是:将item中的每个成员用str分隔开,再拼成一个完整的字符串。
    所以直接result.append(".".join(path))就可以实现用.分隔四个整数的功能了

    class Solution:
        def isOK(self, s, start, end):
            if start > end:
                return False
            if s[start] == '0' and start != end:
                return False
            if 0 <= int(s[start:end+1]) <= 255:
                return True
    
        def backtracking(self, s, startIndex, path, result):
            if startIndex == len(s) and len(path) == 4:
                result.append(".".join(path))
                return
    
            # 单层递归逻辑
            # 每次切割的位置向后移一个
            for i in range(startIndex, len(s)):
                if self.isOK(s, startIndex, i):
                    path.append(s[startIndex:i + 1])
                    self.backtracking(s, i + 1, path, result)
                    path.pop()
    
        def restoreIpAddresses(self, s: str) -> List[str]:
            if len(s) < 4 or len(s) > 12:
                return []
            result = []
            self.backtracking(s, 0, [], result)
            return result
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    总的来说,这道题思路还算好想,但是代码不是很好写。
    要注意的点很多,我在自己想的时候就漏掉了段位里有非正整数字符不合法的情况。

    78. 子集

    举例:nums = [1,2,3,4]
    输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3],[4],[1,4],[2,4],[3,4],[1,2,4],[1,3,4],[2,3,4],[1,2,3,4]]

    [1,2,3,4]
    [1]
    [2]
    [3]
    [4]
    [1,2]
    [1,3]
    [1,4]
    [2,3]
    [2,4]
    [3,4]
    [1,2,3]
    [1,2,4]
    [1,3,4]
    [2,3,4]
    [1,2,3,4]

    这道题result中子集的数量一定是2(len(nums))
    每个result中都有[]这一子集
    本题是找树的所有节点!
    第一次传入的path就是[],直接就被result.append(path[:])了,所以不用担心漏掉[]

    class Solution:
        def backtracking(self, nums, startIndex, result, path):
            result.append(path[:])
            if startIndex >= len(nums):
                return
            for i in range(startIndex, len(nums)):
                path.append(nums[i])
                self.backtracking(nums, i + 1, result, path)
                path.pop()
    
        def subsets(self, nums: List[int]) -> List[List[int]]:
            result = []
            self.backtracking(nums, 0, result, [])
            return result
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    本题无需考虑顺序问题,因为不管什么顺序都会全都遍历到,比如:nums = [3,1,2]
    输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

    [3,1,2]
    [3]
    [1]
    [2]
    [3,1]
    [3,2]
    [1,2]
    [3,1,2]

    90. 子集 II

    解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列

    [1]
    [2]
    [1,2]
    [2,2]
    [1,2,2]
    [1,2,2]
    [2]
    [1,2]

    我觉得,对上一题加个判断条件就OK了

    class Solution:
        def backtracking(self, nums, startIndex, result, path):
            result.append(path[:])
            if startIndex >= len(nums):
                return
            for i in range(startIndex, len(nums)):
                # 对同一树层使用过的元素进行跳过
                if nums[i - 1] == nums[i] and i > startIndex:
                    continue
                path.append(nums[i])
                self.backtracking(nums, i + 1, result, path)
                path.pop()
    
        def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
            result = []
            path = []
            nums.sort()
            self.backtracking(nums, 0, result, path)
            return result
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    今天的最后一道题,明明不难,但是由于傻乎乎的复制了上一题的代码改了改就提交了,错了好几次,最后才注意到两道题函数名不一样。。。

  • 相关阅读:
    .NET周刊【11月第3期 2023-11-19】
    Windows下启动freeRDP并自适应远端桌面大小
    计算机毕业设计springboot+vue基本微信小程序的学习资料共享小程序
    uniapp产品编辑页-图片上传后回显编辑-组件uni-file-picker显示之前已上传的图片 + 头像图片原地覆盖上传示例
    SSM+Mysql实现的共享单车管理系统(功能包含分角色,登录、用户管理、服务点管理、单车管理、分类管理、学生信息管理、单车租赁、信息统计、系统设置等)
    CSS3基础
    微机期末复习指导
    第03章 第03章 栈
    微软校园大使喊你来秋招啦!
    微信每日早安推送 Windows版
  • 原文地址:https://blog.csdn.net/qq_51762665/article/details/132720270