代码随想录 - 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

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

上面那种写法我能理解,但是和我自己的思路还是不太一样。
下面这种写法就比较符合我的思路。
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

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

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

本题无需考虑顺序问题,因为不管什么顺序都会全都遍历到,比如: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

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

你可能感兴趣的:(做题,python,leetcode,算法)