一、复习
先复习一下昨天写的回溯问题,从全排列这个简单的开始。
46、 全排列
其实这种回溯挺好玩的,不同于动态规划,他可以记住answer的细节,并且都在路径上(我们用不断递归path来记录这个值,最后把最终答案append到res中)。
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
def dfs(path,res,nums):
if len(nums)==0:
res.append(path)
return
for i in nums:
tmp_nums=[]
for j in nums:
if j!=i:
tmp_nums.append(j)
dfs(path+[i],res,tmp_nums)
path=[]
res=[]
dfs(path,res,nums)
return res
39、 组合总和
自己写出来了!好棒啊!
就是有三个问题:
(1)定义递归函数后,调用函数,不要马虎写在函数里面
(2)为了避免不和前面的序列重复,前面选过的,在后面就不能选了,因此设置了index,每一个分支只能从自己对应的index开始选。即最左边都可以选,再网游一个,就不能选第一个了;不能选第二个,逐渐缩小范围。因为不分顺序,所以最左边像一个全集,而最右边就像一个只有一个元素的集合。这样分支肯定不是所有可能情况,比如说最小的或者中等的单独一个的集合,但是这种集合其实在前面也讨论可能性了,前面并不排除最小和中等独当一面。
(3)为了避免过于复杂,涉及到剪枝,所以不要忘记排序!
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
def dfs(index,size,candidates,res,path,target):
for i in range(index,size):
residue = target-candidates[i]
if residue<0:
break
elif residue==0:
path+=[candidates[i]]
res.append(path)
return
else:
dfs(i,size,candidates,res,path+[candidates[i]],residue)
path=[]
res=[]
candidates.sort()
dfs(0,len(candidates),candidates,res,path,target)
return res
40. 组合总和 II
1、刚开始又忘记要sort()了,但是还有一个问题,重复的被我省略了
class Solution:
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
def dfs(index,size,res,path,target,candidates):
if index==size:
return
for i in range(index,size):
if i!=0 and candidates[i]==candidates[i-1]:
continue
residue = target-candidates[i]
if residue<0:
break
elif residue==0:
res.append(path+[candidates[i]])
return
else:
dfs(i+1,size,res,path+[candidates[i]],residue,candidates)
res =[]
path = []
candidates.sort()
dfs(0,len(candidates),res,path,target,candidates)
return res
注释掉又出现重复的问题
2、加入了一句判断:看这个数是否在路径中已经存在。因为我们允许竖着重复,不允许横着重复
class Solution:
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
def dfs(index,size,res,path,target,candidates):
if index==size:
return
for i in range(index,size):
if i!=0 and candidates[i]==candidates[i-1] and candidates[i] not in path:
continue
residue = target-candidates[i]
if residue<0:
break
elif residue==0:
res.append(path+[candidates[i]])
return
else:
dfs(i+1,size,res,path+[candidates[i]],residue,candidates)
res =[]
path = []
candidates.sort()
dfs(0,len(candidates),res,path,target,candidates)
return res
3、直接去重超时了,看看答案叭
高赞给的方法是这样的,直呼绝绝子:
if i>index and candidates[i]==candidates[i-1]:
如果不是一行中的第一个(i>index)并且还有等于前面的数值,即前面已经讨论过,就忽略了。如果是一行中的第一个,等于前面的数值,相当于是下一层分支的情况,可以保留。用是否为一层的第一个来区分,真的绝绝子,而这个判断条件为i>index。
三、47. 全排列 II
和40题相似,比40还要简单一些,也是横着不能要(结果重复了),但是竖着可以要(因为数组中包含了重复的数值,这些重复的数值,可以被使用相应的次数)
又忘记sort了。如果不sort,就无法有效排除那些重复的组合,因为那些重复的组合就不相邻,那判断就无效了。
这道题比较烦人的还是数组的删除元素。如果直接删除的话,nums没遍历完成就没元素了,所以要先深拷贝出来。注意:删除后,整体的size会变,当size=0时,就添加路径,并返回。
这题判断横着重复还是竖着重复和上题相似。
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
def dsf(size, path, res, nums):
for index in range(size):
if index >0 and nums[index] == nums[index - 1]:
continue
else:
tmp_num = []
for i in nums:
tmp_num.append(i)
new_path = tmp_num[index]
tmp_num.pop(index)
size = len(tmp_num)
if size==0:
res.append(path+[new_path])
return
dsf(size, path + [new_path], res, tmp_num)
path = []
res = []
nums.sort()
dsf(len(nums), path, res, nums)
return res
3、看下别人怎么删除列表中的元素的。
用的是这种切片nums[:i]+nums[i+1:],开辟了新的内存空间
把自己的改成切片居然超时了,我不理解。奥,删除操作没成功,应该从index+1开始。【小声:好智障啊】
成功了!用切片实现了删除操作。但我感觉这道题的思路还不清晰,毕竟前面磕磕绊绊的~鹅鹅鹅,明天再做一遍,整理一下思路。
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
def dsf(size, path, res, nums):
for index in range(size):
if index >0 and nums[index] == nums[index - 1]:
continue
else:
new_path = nums[index]
tmp_num =nums[:index]+nums[index+1:]
size = len(tmp_num)
if size==0:
res.append(path+[new_path])
return
dsf(size, path + [new_path], res, tmp_num)
path = []
res = []
nums.sort()
dsf(len(nums), path, res, nums)
return res