代码随想录算法训练营第二十七天| LeetCode39. 组合总和、LeetCode40.组合总和II、LeetCode131.分割回文串

39. 组合总和

题目描述: 39. 组合总和.

解法

回溯
class Solution(object):
    def backtrack(self,startindex,path,last):
        if last == 0:
            self.res.append(path[:])
        if last<0:
            return
        for startindex in range(startindex,len(self.candidates)):
            num = self.candidates[startindex]
            path.append(num)
            self.backtrack(startindex,path,last-num)
            path.pop()
    def combinationSum(self, candidates, target):
        self.candidates = candidates
        self.res = []
        self.backtrack(0,[],target)
        return self.res

没什么难度,但是要注意为了避免重复例如[2,2,3]和[2,3,2]这类的重复,每次到下面的回溯,起点位置应该等于当前回溯的起点位置,这样能保证遍历的顺序。

40.组合总和II

题目描述: 40.组合总和II.

解法

回溯
class Solution(object):
    def backtrack(self,start,path,last):
        if last == 0:
            self.res.append(path[:])
            return
        if last < 0:
            return
        for i in range(start,len(self.candidates)):
            if i>start and self.candidates[i] == self.candidates[i-1]:
                continue
            num = self.candidates[i]
            path.append(num)
            self.backtrack(i+1,path,last-num)
            path.pop()
    
    def combinationSum2(self, candidates, target):
        self.candidates = candidates
        self.candidates.sort()
        self.res = []
        self.backtrack(0,[],target)
        return self.res

这个题的难点有两个:
1.如何能够避免一个元素被两次调用:这个问题相对来说比较好解决,只需要进入下一层回溯的时候,起点位置向后移一位即可
2.如何能避免相同的集合由于顺序不同都存放在res中:这个问题就需要考虑的比较多了,首先是要考虑为什么会出现这样的情况,我们知道根据1的内容,不会存在两个相同位置的元素出现在同一个path中,但有可能出现两个不同位置的元素是相同的内容,这样就存在了相同的path,只不过内部的顺序不同,那如果想要避免这个情况,就要从同级回溯的角度去思考,为了避免同级有重复,可以先将数组排序,然后在同级的位置,也就是每一层的回溯中,去除重复的元素,这样可以避免path内容相同但顺序不同的情况

131.分割回文串

题目描述: 131.分割回文串.

解法

回溯
class Solution(object):
    def ishui(self,s):
        left,right = 0,len(s)-1
        while left < right:
            if s[left] != s[right]:
                return False
            left += 1
            right -= 1
        return True
    
    def backtrack(self,start,path):
        if start == self.length:
            self.res.append(path[:])
            return
        for i in range(start+1,self.length+1):
            if not self.ishui(self.s[start:i]):
                continue
            path.append(self.s[start:i])
            self.backtrack(i,path)
            path.pop()
        

    def partition(self, s):
        self.s = s
        self.length = len(s)
        self.res = []
        self.backtrack(0,[])
        return self.res

这个问题复杂一些,但是分割问题本质上也就是组合问题,将字符串中的字符以各种不同的方式进行组合,看看哪些组合中的所有子字符串都是回文串就好。
那么每一层最开始的分割,都是先将字符串的第一个子字符分割出去,然后剩下的进入到下一层的回溯分割中。
先分一个,再分两个,再分三个,直到一口气把当前层的字串整体分割下来,这里其实有个剪枝的技巧,就是如果先分割出来的子串并非是回文串,那么就不需要再将后面的字串进入到下一层回溯中。
举个例子,假如某一层的子串是“abc”,那么就先将“a"分割出来,然后”bc“进入到下次层回溯,由于"a"是回文串,所以可以让它后续的子串进入到下一层回溯中,假设分割"a"的所有回溯完成了,那么就该开始"ab",但是"ab"并非是回文串,所以也不用将"c"进入到下一层。
这里还有一个重点就是,每一次都向下传递什么内容,首先path是一定要传递的,否则无法最终返回正确的分割结果。另外就是传递的子串,每一层处理的子串都是不同的,那么如何区分不同呢?其实传递切片也是可以的,但是就需要额外的空间。我们完全可以在原有的整个字符串上使用索引进行传递,然后在path中添加切片。具体是如何操作呢?
以"aab"为例子,我们最初的start位置肯定是0,在第一轮中用i进行遍历,处理[start,i]这个范围的切片,那么进入到下一层之后,假设下一层用j进行遍历,那么处理的就是[i,j]这个范围的切片,由此不难看出,每次遍历的切片起点,就是上次层的i,由此可以得到,i作为下一层的start进行传递,那每一层的i终点在哪呢?我们可以知道,每一层最大遍历范围就是将整个子串全部遍历,那么假设这一层的起点是start,若是想用切片[start,i]来表示全部的子串,那么i就一定等于整个字符串的length,也就是说每层i的遍历范围是range(start+1,length+1),这样i最终会到达length这个地方,那么切片[start,length]就是剩余的全部子串。这里还有一个要注意的是,为什么i的遍历开始是start+1,使用start+1可以保证每次遍历至少有一个字符被切割出来。
最后是终止条件,其实中止条件完全可以用每一层的start来表示,刚才提到了[start,start+1]是第一个被切割出来的字符,那么如果start已经等于整体的length了,那就说明已经没法再进行切割了,也就是说子串已经被全部切割成回文小子串了,就可以将当前的path加入到最后的res中了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值