秋招算法备战第27天 | 39. 组合总和、40.组合总和II、131.分割回文串

39. 组合总和 - 力扣(LeetCode)

这题的关键在于去重,重点为通过start_index控制开始遍历的元素不在已经

class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        result = []
        path = []

        def backtracking(start_index):
            if sum(path) == target:
                result.append(path[:])
                return
            elif sum(path) > target:
                return
            
            for i in range(start_index, len(candidates)):
                path.append(candidates[i])
                backtracking(i)
                path.pop()
        
        backtracking(0)
        return result

下面是GPT4对代码的优化

您的代码已经相当清晰和高效了,但还有一些小优化可以实施。主要的优化是减少多次计算
sum(path),我们可以通过传递当前的和到递归调用中来减少这些额外的计算。下面是优化后的代码:

    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        result = []
        path = []

        def backtracking(start_index, target):
            if target == 0:
                result.append(path[:])
                return
            elif target < 0:
                return
            
            for i in range(start_index, len(candidates)):
                path.append(candidates[i])
                backtracking(i, target - candidates[i])
                path.pop()
        
        backtracking(0, target)
        return result 

在这个版本的代码中,我引入了一个新的参数 targetbacktracking
函数中,用来表示目标值的当前余额。这意味着我们不需要多次计算 sum(path),因为每次我们添加一个新的数字到
path,我们只需从 target 中减去这个数字。如果 target 成为 0,这就意味着我们找到了一个有效的组合。如果
target 变为负数,这就意味着我们已经超过了目标值,我们可以停止进一步的搜索。

40. 组合总和 II - 力扣(LeetCode)

下面的代码通过了测试样例,但是超时了,主要在于对连续重复数字怎么减少不必要的重复判断,这块没能想出来

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

        result = []
        path = []

        def backtracking(start_index, rest):
            if rest == 0:
                if path not in result:
                    result.append(path[:])
                return
            elif rest < 0:
                return

            for i in range(start_index, len(candidates)):
                path.append(candidates[i])
                backtracking(i+1, rest-candidates[i])
                path.pop()
        
        backtracking(0, target)
        return result

GPT4的优化如下

你的当前解决方案包含一些冗余的步骤,尤其是检查 path 是否已经在 result 中。这个操作是 O(n)
的时间复杂度,这使得整个算法的时间复杂度过高。而且,我们可以简单地跳过重复的数字来消除结果中的重复组合,从而避免了对结果列表的查找。

以下是修改后的代码,改进了这两点,降低了时间复杂度:

    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
        candidates.sort()
        result = []
        path = []

        def backtracking(start_index, rest):
            if rest == 0:
                result.append(path[:])
                return
            elif rest < 0:
                return

            for i in range(start_index, len(candidates)):
                # 跳过相同的元素,避免结果中的重复
                if i > start_index and candidates[i] == candidates[i-1]:
                    continue
                
                path.append(candidates[i])
                backtracking(i+1, rest-candidates[i])
                path.pop()
        
        backtracking(0, target)
        return result ```

在这个修改过的版本中,我们在开始新的递归调用之前,检查当前的元素是否和上一个元素相同。如果是,我们就跳过这个元素,以此来避免在结果中生成重复的组合。这使得我们无需在将
`path` 添加到 `result` 之前检查 `path` 是否已经在 `result` 中。

这部分自己之前写的判断条件是 if i > 0 and candidates[i] == candidates[i-1]:,这样的话会把满足条件的连续数组给去掉,而使用i>startindex则保证了当前的连续的下一个是可以被遍历的。

131. 分割回文串 - 力扣(LeetCode)

这题是看了视频才理解,这里是得到所有符合题意的切割方案,将start_index作为切割点,在切割之前就先判断是否符合回文串,所以在最后保存结果的时候条件为切割点在字符末尾,而不用判断是否满足题意。自己按照该思路撰写的代码如下

class Solution:
    def partition(self, s: str) -> List[List[str]]:
        def is_huiwen(sub_s):
            return sub_s == sub_s[::-1]
        
        result = []
        path = []
        n = len(s)
        
        def backtracking(start_index):
            if start_index >= n:
                result.append(path[:])
                return

            for i in range(start_index+1, n+1):
                sub_s = s[start_index:i]
                if is_huiwen(sub_s):
                    path.append(sub_s)
                    backtracking(i)
                    path.pop()

        backtracking(0)
        return result

参考代码如下

class Solution:

    def partition(self, s: str) -> List[List[str]]:
        result = []
        self.backtracking(s, 0, [], result)
        return result

    def backtracking(self, s, start_index, path, result ):
        # Base Case
        if start_index == len(s):
            result.append(path[:])
            return
        
        # 单层递归逻辑
        for i in range(start_index, len(s)):
            # 若反序和正序相同,意味着这是回文串
            if s[start_index: i + 1] == s[start_index: i + 1][::-1]:
                path.append(s[start_index:i+1])
                self.backtracking(s, i+1, path, result)   # 递归纵向遍历:从下一处进行切割,判断其余是否仍为回文串
                path.pop()             # 回溯

总结

start_index的控制、去重以及回溯前的条件判断是今天题目的精髓

附录

代码随想录算法训练营第二十七天 | 39. 组合总和、40.组合总和II、131.分割回文串_小蛙先森的博客-CSDN博客

你可能感兴趣的:(算法)