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中了。