代码随想录算法训练营第30天|回溯算法part03| 39. 组合总和 、40.组合总和II 、131.分割回文串

代码随想录算法训练营第30天|回溯算法part03| 39. 组合总和 、40.组合总和II 、131.分割回文串

39. 组合总和

39. 组合总和

自己做

思路:

本身没有什么思路,但是按照卡哥的套路进行画树,慢慢的就知道了其中的归路!

有两个问题:

  1. 什么时候终止递归
  2. 对于重复的怎么处理,比如递归时会出现2,2,3和3,2,2这样的情况,仅需要一条路径就可以

手写画图如下:

请添加图片描述
代码:

python

class Solution(object):
    def __init__(self):
        self.result = []
        self.path = []
    def combinationSum(self, candidates, target):
        """
        :type candidates: List[int]
        :type target: int
        :rtype: List[List[int]]
        """
        self.backtracking(candidates, target, 0, 0)
        return self.result

    def backtracking(self, candidates, target, curSum, startIndex):
        if curSum == target:
            self.result.append(list(self.path))
            return 
        elif curSum > target:
            return
        
        for index in range(startIndex, len(candidates)):
            curSum += candidates[index]
            self.path.append(candidates[index])
            self.backtracking(candidates, target, curSum, index)
            self.path.pop()
            curSum -= candidates[index]

        return


代码随想录

思路:

思路是一样的

剪支:

其实如果已经知道下一层的sum会大于target,就没有必要进入下一层递归了。

40. 组合总和 II

40. 组合总和 II

自己做

不会去除重复的组合

代码随想录

思路:

本题的难点在于区别2中:集合(数组candidates)有重复元素,但还不能有重复的组合。

都知道组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上使用过,一个维度是同一树层上使用过。没有理解这两个层面上的“使用过” 是造成大家没有彻底理解去重的根本原因。

所以我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重。

强调一下,树层去重的话,需要对数组排序

回溯三部曲

  • 递归函数参数

此题还需要加一个bool型数组used,用来记录同一树枝上的元素是否使用过。

这个集合去重的重任就是used来完成的。

def backtracking(self, candidates, target, curSum, startIndex, used):
  • 递归终止条件
if curSum == target:
            self.result.append(list(self.path))
            return 
        elif curSum > target:
            return

sum > target 这个条件其实可以省略,因为在递归单层遍历的时候,使用剪枝的操作就不用了,没用剪支就得加上

  • 单层搜索的逻辑

这里与39.组合总和 (opens new window)最大的不同就是要去重了。

前面我们提到:要去重的是“同一树层上的使用过”,如何判断同一树层上元素(相同的元素)是否使用过了呢。

如果candidates[i] == candidates[i - 1] 并且 used[i - 1] == false,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1]。

此时for循环里就应该做continue的操作。

这块比较抽象,如图:

在这里插入图片描述

我在图中将used的变化用橘黄色标注上,可以看出在candidates[i] == candidates[i - 1]相同的情况下:

used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
used[i - 1] == false,说明同一树层candidates[i - 1]使用过

可能有的录友想,为什么 used[i - 1] == false 就是同一树层呢,因为同一树层,used[i - 1] == false 才能表示,当前取的 candidates[i] 是从 candidates[i - 1] 回溯而来的。

而 used[i - 1] == true,说明是进入下一层递归,去下一个数,所以是树枝上,如图所示:

在这里插入图片描述

代码:

python(自己写的)

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

    def combinationSum2(self, candidates, target):
        """
        :type candidates: List[int]
        :type target: int
        :rtype: List[List[int]]
        """
        candidates = sorted(candidates)
        #print(candidates)
        used = [0]*len(candidates)
        self.backtracking(candidates, target, 0, 0, used)
        return self.result

    def backtracking(self, candidates, target, curSum, startIndex, used): # 1.
    	# 2. 这里没用剪支,所以得加上curSum > target:
        if curSum == target:
            self.result.append(list(self.path))
            return 
        elif curSum > target:
            return
        
        # 3. 
        for index in range(startIndex, len(candidates)):
            if index > 0 and candidates[index] == candidates[index-1] and used[index-1] == 0:
                continue
            used[index] = 1
            curSum += candidates[index]
            self.path.append(candidates[index])
            self.backtracking(candidates, target, curSum, index+1, used)
            self.path.pop()
            curSum -= candidates[index]
            used[index] = 0

        return

131. 分割回文串

131. 分割回文串

自己做

做不出来

代码随想录

思路:

本题这涉及到两个关键问题:

  1. 切割问题,有不同的切割方式
  2. 判断回文

其实切割问题类似组合问题

  • 组合问题:选取一个a之后,在bcdef中再去选取第二个,选取b之后在cdef中再选取第三个…。
  • 切割问题:切割一个a之后,在bcdef中再去切割第二段,切割b之后在cdef中再切割第三段…。

startIndex即可作为切割线

所以切割问题,也可以抽象为一棵树形结构,如图:

在这里插入图片描述

在切割的过程中,一旦发现不是回文直接返回,如果是一直是回文,那就直到切割到最后就返回其组合

树的宽度可以表示每条切割线的位置,树的深度表示切割线的条数

回溯三部曲

  • 递归函数参数和返回值

def backtracking(self, s, startIndex)

  • 递归函数终止条件

if startIndex == len(s): self.result.append(list(self.path))

在这里插入图片描述

从树形结构的图中可以看出:切割线切到了字符串最后面,说明找到了一种切割方法,此时就是本层递归的终止条件。

  • 单层搜索的逻辑

在for (int i = startIndex; i < s.size(); i++)循环中,我们 定义了起始位置startIndex,那么 [startIndex, i] 就是要截取的子串。

首先判断这个子串是不是回文,如果是回文,就加入在vector path中,path用来记录切割过的回文子串。

for index in range(startIndex, len(s)):
            # [startIndex:index]为子串
            if self.isPartition(s, startIndex, index): 
                self.path.append(s[startIndex:index+1])
            else:
                continue

            self.backtracking(s, index+1)
            self.path.pop()
        return 

注意切割过的位置,不能重复切割,所以,backtracking(s, i + 1); 传入下一层的起始位置为i + 1。

代码:

python

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

    def partition(self, s):
        """
        :type s: str
        :rtype: List[List[str]]
        """
        self.backtracking(s, 0)
        return self.result


    def backtracking(self, s, startIndex):# 1. 
        # 2.
        if startIndex == len(s):
            self.result.append(list(self.path))

        # 3. 
        for index in range(startIndex, len(s)):
            # [startIndex:index]为子串
            if self.isPartition(s, startIndex, index): 
                self.path.append(s[startIndex:index+1])
            else:
                continue

            self.backtracking(s, index+1)
            self.path.pop()
        return 

    def isPartition(self, s, startIndex, index):
        left = startIndex
        right = index
        while left < right:
            if s[left] == s[right]:
                left += 1
                right -= 1
            else:
                return False
        
        return True



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值