回溯篇 - 子集问题
题目链接
组合问题和分割问题都是收集树的叶子节点
而子集问题是找树的所有节点
子集是无序的,子集{1,2} 和 子集{2,1}是一样的。
那么既然是无序,取过的元素不会重复取,写回溯算法的时候,for就要从startIndex开始,而不是从0开始
什么时候for可以从0开始呢?求排列问题的时候,就要从0开始
从图中红线部分,可以看出遍历这个树的时候,把所有节点都记录下来,就是要求的子集集合。
写子集问题,可以不用写出口,一方面是因为收集所有叶子结点,另一方面startIndex >= nums.size(),本身本层for循环本来也结束了。
如果要加出口,if start >= nums.size(): return, 一定要加在res.append(path)之后,否则会漏掉自己
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
res = []
def rec(start,path):
res.append(path)
for i in range(start,len(nums)):
rec(i+1,path+[nums[i]])
rec(0,[])
return res
题目链接
就是子集加了个每一层上的去重
class Solution:
def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
res = []
nums.sort()
def rec(start, path):
res.append(path)
for i in range(start, len(nums)):
if i > start and nums[i] == nums[i-1]: continue
rec(i+1, path+[nums[i]])
rec(0, [])
return res
题目链接
非常有陷阱的子集问题:
这个递增子序列比较像是取有序的子集。而且本题也要求不能有相同的递增子序列。
这又是子集,又是去重,是不是不由自主的想起了90.子集2, 在那题里,采取了对数组先排序,然后再去重的套路
if i > start and nums[i] == nums[i-1]: continue
但是这道题是求数组里的递增子序列,不能破坏原数组结构,那怎么办呢?
首先我们还是要明确,这个去重是在每层递归逻辑里去重,即每层选过了的元素我们不能再选,如图:
我们这道题的在每层遍历逻辑里去重,可以采用unordered set 或者数组就行,把遍历到的nums[i],装进数组,如果在这一层再次遍历到同一数字,那么continue就行了,这样就实现了每层的去重逻辑。
这种做法要记住
这道题的出口也有讲究,子集问题,原本不用加出口限制,收集所有结点path, 但是这道题是递增子序列,需要len(path) >= 2我们再把它加进结果集。
class Solution:
def findSubsequences(self, nums: List[int]) -> List[List[int]]:
res = []
def rec(start, path):
if len(path) >= 2:
res.append(path)
hashmap = []
for i in range(start, len(nums)):
if nums[i] not in hashmap:
hashmap.append(nums[i])
else: continue
if not path or nums[i] >= path[-1]:
rec(i+1, path+[nums[i]])
rec(0, [])
return res