Bootstrap

代码随想录 Day 29 | 【第七章 回溯算法 part02】39. 组合总和、40.组合总和II、131.分割回文串

一、39. 组合总和

本题是 集合里元素可以用无数次,那么和组合问题的差别 其实仅在于 startIndex上的控制

题目链接/文章讲解:代码随想录

视频讲解:带你学透回溯算法-组合总和(对应「leetcode」力扣题目:39.组合总和)| 回溯法精讲!_哔哩哔哩_bilibili

(1)定义函数的参数和返回值:定义二维数组result存放结果集,定义一维数组path收集单一结果,返回值为空。参数有数组、target、sum(统计组合的和)、startindex(用于找到下一个组合的开始位置,也就是找到下一个for循环的开始位置)

(2)确定终止条件:如果sum大于target,那么直接return,终止;如果sum等于target,那么说明满足题目条件,将这个path存入结果集,并返回。

(3)单层搜索逻辑:for循环,i从startindex开始,一直循环到等于数组长度,i就是收集每一个元素的过程,所以将每一个元素添加进入path中;然后更新sum,将这个元素加入sum;接下来进行递归,传入的start index依然是从i开始,因为组合里的元素可以重复;回溯过程,sum减出刚才的元素,然后将path清空。

class Solution:
    def __init__(self):
        self.result = []
        self.path = []

    def backTracking(self, candidates, target, sum, startindex):
        # 终止条件:
        if sum > target:
            return
        if sum == target:
            self.result.append(self.path[:])
            return 
        # 单层递归条件
        for i in range(startindex, len(candidates)):
            self.path.append(candidates[i])
            sum += candidates[i]
            self.backTracking(candidates, target, sum, i)
            sum -= candidates[i]
            self.path.pop()

    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        self.backTracking(candidates, target, 0, 0)
        return self.result

二、40.组合总和II

本题开始涉及到一个问题了:去重。

注意题目中给我们 集合是有重复元素的,那么求出来的 组合有可能重复,但题目要求不能有重复组合。

题目链接/文章讲解:代码随想录

视频讲解:回溯算法中的去重,树层去重树枝去重,你弄清楚了没?| LeetCode:40.组合总和II_哔哩哔哩_bilibili

1. 本题和组合总和Ⅰ、组合总和Ⅲ的区别

        给出的数组中有重复的元素,所以在组合过程中会出现相同组合,此时需要对相同组合进行去重;并且因为给出的数组有重复的元素,在组合中会出现相同元素的组合,但是其实来自数组的不同元素而已,需要注意。

2. 去重的逻辑:树层去重 + 树枝去重

        由于给出的数组中会含有重复的元素,所以在组合过程中,重复元素里,第一个元素得到的组合,会和与自己重复的元素得到的组合重复,所以需要进行去重。

        因此,将数组进行排序,将重复元素放在一起,便于组合过程中去重。

3. 代码实现

(1)定义result用于存放结果集,path用于收集单条结果。

(2)定义递归函数的参数和返回值:返回值为空;参数nums是题目给定的数组,target为目标和,sum用于记录单条路径的和,startindex用于防止组合里使用相同元素,used数组用于标记哪些元素使用过(使用过为1,未使用为0)。

(3)确定终止条件:sum大于target,则返回;sum等于target,那么满足条件,将path放入result。

(4)单层递归逻辑:for循环从startindex开始遍历,一直到nums数组长度-1结束。

去重:如果当前元素等于前一个元素(注意,数组需要排序),并且used[i-1]等于0,只有此时才需要进行树层的去重,如果used[i-1]等于1,那么属于树枝的状态,不需要去重,否则会排除两个相同元素出现在一个组合的情况;遇到上述情况直接continue,不进行处理。

        接下来,path收集元素,sum加上这个数,并且将used赋成true;

        递归,传入参数,其中startindex传为i+1。

        回溯:sum减去这个数,path弹出元素,used赋成false。

class Solution:
    def __init__(self):
        self.result = []
        self.path = []

    def backTracking(self, candidates, target, sum, startindex, used):
        # 确定终止条件:
        if sum > target:
            return
        if sum == target:
            self.result.append(self.path[:])
            return
        # 单层递归逻辑:
        for i in range(startindex, len(candidates)):
            # 去重:
            if i > startindex and candidates[i] == candidates[i-1] and used[i-1] == 0:
                continue
            # 递归:
            self.path.append(candidates[i])
            sum += candidates[i]
            used[i] = 1
            self.backTracking(candidates, target, sum, i+1, used)
            # 回溯:
            sum -= candidates[i]
            used[i] = 0
            self.path.pop()

    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
        used = [0] * len(candidates)
        candidates.sort()
        self.backTracking(candidates, target, 0, 0, used)
        return self.result
        

三、131.分割回文串

本题较难 ,大家先看视频来理解 分割问题,明天还会有一道分割问题,先打打基础。

回文 串是向前和向后读都相同的字符串。

(1)定义一维数组path收集单个组合,二维数组result存储结果集。

(2)定义递归函数参数和返回值:参数有字符串s,int型startindex控制下一次切割的位置。

(3)确定终止条件:切割到字符串最后就终止,startindex就是切割线。如果startindex大于等于s的长度,那么终止递归,将path放入result。

(4)单层递归逻辑:for循环,从startindex到字符串长度结束。子串的范围是[startindex, i],因为startindex是固定值不动,而i一直在++,所以它是切割的子串,因此需要判断它是否是回文的。

判断回文子串:定义一个新函数,传入子串、起始位置、终止位置;如果是回文,就将区间加入result,如果不是回文,就直接跳过。

递归:起始位置从i+1开始。

回溯:path.pop()

class Solution:
    def __init__(self):
        self.result = []
        self.path = []

    def isPalindrome(self, s, left, right):
        i = left
        j = right
        while i <= j:
            if s[i] != s[j]:
                return False
            i += 1
            j -= 1
        return True

    def backTracking(self, s, startindex):
        # 定义终止条件:
        if startindex == len(s):
            self.result.append(self.path[:])
            return
        
        # 单层递归条件:
        for i in range(startindex, len(s)):
            if self.isPalindrome(s, startindex, i):
                self.path.append(s[startindex:i+1])
                self.backTracking(s, i+1)
                self.path.pop()
            
    def partition(self, s: str) -> List[List[str]]:
        self.backTracking(s, 0)
        return self.result

;