Day 27 回溯:39|40. 组合总和 I II, 131. 分割回文串

39. 组合总和

  • 思路
    • example
    • 回溯:深度由sum_ vs target控制,宽度由startIndex控制。
    • candidates中无重复元素
      • 不需要考虑同层去重
    • 无限制重复被选取 (有放回)
      • startIndex取当前位置
    • "去重“:排序 + startIndex

无重可复选
注意:本题不需预先排序。

  • 剪枝
    • 模向 (for 循环内)
    • 纵向 (递归函数base case)
  • 2 <= candidates[i] <= 40
  • 复杂度. 时间:O(?), 空间: O(?)
class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        def backtrack(candidates, start, sum_):
            if sum_ == target: 
                res.append(path[:])
                return 
            for i in range(start, len(candidates)):
                if sum_ + candidates[i] > target:
                    break  
                path.append(candidates[i])
                sum_ += candidates[i]
                backtrack(candidates, i, sum_)
                sum_ -= candidates[i]
                path.pop()
        candidates.sort()
        res, path = [], []
        backtrack(candidates, 0, 0)
        return res 
class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        def backtrack(candidates, start, sum_):
            if sum_ > target: 
                return 
            if sum_ == target:
                res.append(path[:])
                return 
            for i in range(start, len(candidates)):
                # 在这里提早剪枝更好一些
                path.append(candidates[i])
                sum_ += candidates[i] 
                backtrack(candidates, i, sum_)
                sum_ -= candidates[i] 
                path.pop()
        res, path = [], []
        backtrack(candidates, 0, 0)
        return res 
class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        def backtrack(start):
            if path and sum(path) == target:
                res.append(path[:])
                return 
            if path and sum(path) > target:
                return 
            for i in range(start, len(candidates)):
                path.append(candidates[i])
                backtrack(i) # !!! 可重复取
                path.pop()
        res, path = [], []
        backtrack(0)
        return res 
  • 另一种写法
    • 深度:逐个遍历candidates
    • 宽度:每个candidate选取可能性:0,1,2,。。。
TBA

40. 组合总和 II

  • 思路
    • example
    • candidates有重复元素
      • 同层去重
if i > start and candidates[i] == candidates[i-1]:
                    continue
  • candidates 中的每个数字在每个组合中只能使用 一次
  • 复杂度. 时间:O(?), 空间: O(?)
class Solution:
    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
        def backtrack(candidates, start, sum_):
            if sum_ == target:
                res.append(path[:])
                return 
            for i in range(start, len(candidates)):
                if i > start and candidates[i] == candidates[i-1]:
                    continue 
                if sum_ + candidates[i] > target:
                    break  
                path.append(candidates[i])
                sum_ += candidates[i]
                backtrack(candidates, i+1, sum_)
                sum_ -= candidates[i]
                path.pop()
        res, path = [], []
        candidates.sort()
        backtrack(candidates, 0, 0)
        return res 
class Solution:
    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
        def backtrack(candidates, start, sum_): # sum_ 还没有计入start位置的贡献
            if sum_ > target: # 剪枝
                return 
            if sum_ == target: # 终止条件 
                res.append(path[:])
                return 
            for i in range(start, len(candidates)): 
                if i > start and candidates[i] == candidates[i-1]: # 去重
                    continue 
                # 也可以在这里剪枝
                path.append(candidates[i])
                sum_ += candidates[i]
                backtrack(candidates, i+1, sum_) #  无放回情况
                sum_ -= candidates[i]
                path.pop()
        candidates.sort() # candidates中有重复
        res, path = [], []
        backtrack(candidates, 0, 0)
        return res 
class Solution:
    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
        def backtrack(start):
            if path and sum(path) > target:
                return 
            if path and sum(path) == target:
                res.append(path[:])
                return 
            for i in range(start, len(candidates)):
                if i > start and candidates[i] == candidates[i-1]: # !!!
                    continue 
                path.append(candidates[i])
                backtrack(i+1)
                path.pop()
        candidates.sort() # !!!
        res, path = [], []
        backtrack(0)
        return res 
class Solution:
    def combinationSum3(self, k: int, n: int) -> List[List[int]]:
        def backtrack(start, sum_):
            if sum_ > n:
                return 
            if len(path) == k:
                if sum_ == n:
                    res.append(path[:]) 
                return 
            for i in range(start, 9-(k-len(path))+2):
                path.append(i)  
                sum_ += i  
                backtrack(i+1, sum_)
                sum_ -= i  
                path.pop()
        res, path = [], []
        backtrack(1, 0)
        return res 
  • 小结:组合
    • 去重 (sort, startIndex)
    • 剪枝
      • 个数
      • target, sum_, ...
    • 有放回 vs 无放回
      • startIndex: i vs i + 1
  • 桶装球 vs 球装桶
    • 桶的限制?球的限制?
    • 桶装球:桶视角。深度=桶,宽度=球(可选范围,规格,限制,每次选一个球)
    • 球装桶:球视角。深度=球,宽度=桶(桶的体积,规格,限制,可能取多个球)

131. 分割回文串

  • 思路
    • example
    • 返回 s 所有可能的分割方案: 暴力回溯。
      • 切割 (转化为组合问题)
      • 判断回文 (双指针)


  • 暴力回溯:
    • 桶装球
    • 深度:切割位置 (子串起始位置startIndex),桶
    • 宽度:切割位置 (子串结束位置),球
  • 复杂度. 时间:O(n 2^n), 空间: O(1)
    • worst case: s = ‘aaaaaaaaa’
class Solution:
    def partition(self, s: str) -> List[List[str]]:
        def isPalindrome(s, left, right): # [left, right]
            while left < right:
                if s[left] == s[right]:
                    left += 1
                    right -= 1
                else:
                    return False 
            return True
        def backtrack(s, start):
            if start == len(s):
                res.append(path[:])
                return 
            for i in range(start, len(s)):
                if isPalindrome(s, start, i): 
                    path.append(s[start:i+1])
                    backtrack(s, i+1)
                    path.pop()
        res, path = [], []
        backtrack(s, 0)
        return res  
  • 优化: 动规预处理(O(n^2)):计算所有子串回文与否 (isPalindrome[i][j]: s[i], ..., s[j])
    • 时间:O(n 2^n)
class Solution:
    def partition(self, s: str) -> List[List[str]]:
        def backtrack(s, start):
            if start == len(s):
                res.append(path[:])
                return 
            for i in range(start, len(s)):
                if isPalindrome[start][i] == True:
                    path.append(s[start:i+1])
                    backtrack(s, i+1)
                    path.pop()
            
        n = len(s)
        isPalindrome = [[False for _ in range(n)] for _ in range(n)]
        for i in range(n-1, -1, -1): # 逆序
            for j in range(i, n):
                if s[i] == s[j]:
                    if j - i <= 1:
                        isPalindrome[i][j] = True 
                    else:
                        isPalindrome[i][j] = isPalindrome[i+1][j-1]
        res, path = [], []
        backtrack(s, 0)
        return res 
class Solution:
    def partition(self, s: str) -> List[List[str]]:
        def backtrack(start):
            if start == len(s):
                res.append(path[:])
                return 
            for i in range(start, len(s)):
                if dp[start][i]:
                    path.append(s[start:i+1])
                    backtrack(i+1)
                    path.pop()
        res, path = [], []
        dp = [[False for _ in range(len(s))] for _ in range(len(s))]
        for i in range(len(s)-1, -1, -1):
            for j in range(i, len(s)):
                if i == j:
                    dp[i][j] = True 
                elif j - i + 1 == 2:
                    if s[i] == s[j]:
                        dp[i][j] = True
                else:
                    dp[i][j] = (s[i] == s[j]) and dp[i+1][j-1]
        backtrack(0)
        return res 
  • 进一步优化
    • 动规, 记忆化搜索, 分治?
TBA
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容