代码随想录算法训练营day27 | 39. 组合总和,40.组合总和II,131.分割回文串

39. 组合总和(medium)

  • 思路:集合里元素可以用无数次,那么和#216组合问题的差别 其实仅在于 startIndex上的控制

  • 递归法:

class Solution:
    def __init__(self):
        self.path = []
        self.result = []
        
        
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        return self.bcktracking(candidates, target, 0)
        
        
    def bcktracking(self, candidates, target, startindex):#step1
        #step2
        if sum(self.path) > target:#剪枝
            return
        
        if sum(self.path) == target:
            self.result.append(self.path[:]) #浅拷贝
            return
        #step3
        for i in range(startindex, len(candidates)):
            self.path.append(candidates[i])
            self.bcktracking(candidates, target, i) #每个元素可重复使用,那么i就不需要像#216那样+1了
            self.path.pop()
        return self.result
  • 剪枝

class Solution:
    def __init__(self):
        self.path = []
        self.result = []
        
        
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        candidates.sort()
        return self.bcktracking(candidates, target, 0)
        
        
    def bcktracking(self, candidates, target, startindex):#step1
        #step2
        if sum(self.path) > target:#剪枝
            return
        
        if sum(self.path) == target:
            self.result.append(self.path[:]) #浅拷贝
            return
        #step3
        for i in range(startindex, len(candidates)):
            if sum(self.path) > target: #剪枝,由于对candidates进行排序,那么每添加一个元素进入self.path,就可以check一下是否sum已经大于target了,如果是,那么后面的数只会更大,也就没有必要继续搜索了。
                return
            self.path.append(candidates[i])
            self.bcktracking(candidates, target, i) #每个元素可重复使用,那么i就不需要像#216那样+1了
            self.path.pop()
        return self.result

40.组合总和II(medium)

  • 不难写出收集所有组合的代码,但是有重复的组合出现,但题目要求不可以有重复的组合。因此难点在于去重

  1. 把所有组合求出来,再用set或者map去重,这么做很容易超时!
  2. 题目要求,元素在同一个组合内是可以重复的,但两个组合不能相同。因此,要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重。
  3. 树层去重的话,需要对数组排序!

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

class Solution(object):
    def __init__(self):
        self.path = []
        self.result = []
    
    def combinationSum2(self, candidates, target):
        """
        :type candidates: List[int]
        :type target: int
        :rtype: List[List[int]]
        """
        candidates.sort()
        used = [0]*len(candidates)
        return self.backtracking(candidates, target, 0, used)
        
        
    def backtracking(self, candidates, target, startIndex, used): #step1: used数组用来标记用过的元素
        #step2
        if sum(self.path) > target:
            return
        if sum(self.path) == target:
            self.result.append(self.path[:]) #浅拷贝
            return
        #step3
        for i in range(startIndex, len(candidates)):
            #for loop是树层循环,而去重也是对树层去重,因此接下来就是去重操作
            if (i > 0 and candidates[i] == candidates[i-1]) and used[i-1] == 0:
                continue
            
            used[i] = 1
            self.path.append(candidates[i])
            self.backtracking(candidates, target, i+1, used)
            self.path.pop()
            used[i] = 0
            
        return self.result
  • 也可以(但是使用used标记数组是更好的方式)

class Solution:
    def __init__(self):
        self.path = []
        self.result = []
    
    
    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
        candidates.sort()
        return self.backtracking(candidates, target, 0)
        
    
    def backtracking(self, candidates, target, startIndex):#step1
        #step2
        if sum(self.path) == target:
            self.result.append(self.path[:])#浅拷贝
            return
        #step3
        for i in range(startIndex, len(candidates)):
            if sum(self.path) > target: #剪枝
                return
            
            # 跳过同一树层使用过的元素
            # 注意:这一步的关键在于candidates要排序!
            if i > startIndex and candidates[i] == candidates[i-1]:
                continue
                
            self.path.append(candidates[i])
            self.backtracking(candidates, target, i+1)
            self.path.pop() #回溯
            
        return self.result

131.分割回文串(medium)

  • 难点:字符串的切割实际上与组合(不可使用重复元素)问题逻辑上是一致的。那么切割后的子串就是s[startindex, i]。

在回溯三部曲的step3中,for i in range(startIndex, len(s)), 这样咋一看区间 [startIndex, i] 没有意义,因为i就是startIndex。实际上并不是这样,注意i是要 i+=1的。因此,s[startIndex, i] 就是需要判断是否是回文的子串。

class Solution:
    def __init__(self):
        self.path = []
        self.result = []
    
    
    def partition(self, s: str) -> List[List[str]]:
        return self.backtracking(s, 0)
        
    def backtracking(self, s, startIndex):#step1
        #step2
        if startIndex == len(s):
            self.result.append(self.path[:]) #又忘记需要浅拷贝了
            return
        #step3
        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() #回溯
            else:
                continue
            
            
        return self.result
            
            
    def isPalindrome(self, str, start, end):
        while start < end:
            if str[start] != str[end]:
                return False
            start += 1
            end -= 1
        return True
            

错误点:

  1. 又忘记需要浅拷贝了(由于回溯的原因)
  2. 递归和回溯这两步需要在判断子串是否回文后给出(之前写在了if...else外)

你可能感兴趣的:(算法,数据结构,python,leetcode)