代码随想录|Day23|回溯03|39.组合总和、40.组合总和II、131.分割回文串

39.组合总和

本题和 216.组合总和III 类似,但有几个区别:

  1. 没有元素个数限制:树的深度并不固定,因此递归终止条件有所变化
  2. 每个元素可以使用多次:下层递归的起始位置和上层相同(startIndex不需要改动)

class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        
        def backtrack(startIndex, path, currentSum):
            # 和大于等于目标的时候终止
            # 如果等于目标,还需要先收集
            if currentSum >= target:
                if currentSum == target:
                    result.append(path[:])
                return
            
            for i in range(startIndex, len(candidates)):
                path.append(candidates[i])
                currentSum += candidates[i]
                # 下层递归的起始位置和本层相同
                backtrack(i, path, currentSum)
                path.pop()
                currentSum -= candidates[i]
        
        result = []
        backtrack(startIndex = 0, path = [], currentSum = 0)
        return result

 剪枝:如果我们事先对 candidates 排序,那么下一层递归的 currentSum 一定会更大,在此之前判断 currentSumtarget 的判断可以实现剪枝。

为什么要排序?举个例子,假设总和为 4,那么 [2, 2] 符合条件,下一次搜索可能获得 [2, 1, 1] 也是符合条件的,如果排序则可以确保接下来搜索的元素更大,换句话说 [2, 1, 1] 一定在 [2, 2] 之前被找到。

class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        
        def backtrack(startIndex, path, currentSum):
            # 和大于等于目标的时候终止
            # 如果等于目标,还需要先收集
            if currentSum >= target:
                if currentSum == target:
                    result.append(path[:])
                return
            
            for i in range(startIndex, len(candidates)):
                # 剪枝:如果和已经超过,则不需要继续搜索
                if currentSum + candidates[i] > target: continue

                path.append(candidates[i])
                currentSum += candidates[i]
                # 下层递归的起始位置和本层相同
                backtrack(i, path, currentSum)
                path.pop()
                currentSum -= candidates[i]
        
        result = []
        candidates.sort()
        backtrack(startIndex = 0, path = [], currentSum = 0)
        return result

40.组合总和II

本题的关键是 candidates 中的元素可能重复,如果使用传统的方法递归,则结果很有可能包含重复组合。举个例子,假设 candidates = [1, 2, 2, 5]target = 3。当我们选择了 1,其递归出现 3个 分支:[1, 2]、[1, 2]、[1, 5],此时出现了重复组合。

class Solution:
    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:

        def backtrack(startIndex, path, currentSum):
            if currentSum >= target:
                if currentSum == target:
                    result.append(path[:])
                return
            
            for i in range(startIndex, len(candidates)):
                # 如果当前数字与前一个数字相同,并且不是遍历的第一个数字,则跳过以避免重复组合
                if i > startIndex and candidates[i] == candidates [i-1]:
                    continue
                # 剪枝
                if currentSum + candidates[i] > target:
                    return                

                path.append(candidates[i])
                currentSum += candidates[i]

                backtrack(i + 1, path, currentSum)

                path.pop()
                currentSum -= candidates[i]
        
        result = []
        # 排序
        candidates.sort()
        backtrack(startIndex = 0, path = [], currentSum = 0)
        return result

131.分割回文串

本题可以理解为一个组合问题,我们组合不同的元素,判断是否为回文子串。 

class Solution:
    def partition(self, s: str) -> List[List[str]]:
        # 双指针判断是否为回文串
        def isPalindrome(subs):
            left, right = 0, len(subs)-1
            while left < right:
                if subs[left] != subs[right]:
                    return False
                left += 1
                right -= 1
            return True
        
        def backtrack(startIndex, path):
            # startIndex是我们的切割线
            # 因此递归终止条件为切割到末尾
            if startIndex >= len(s):
                result.append(path[:])
                return
            
            for i in range(startIndex, len(s)):
                # [startIndex, i]为我们的切割子串
                if isPalindrome(s[startIndex: i+1]):
                    path.append(s[startIndex: i+1])
                    backtrack(i+1, path)
                    path.pop()
            
        result = []
        backtrack(startIndex = 0, path = [])
        return result
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值