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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容