491.递增子序列
思路
递归树状图如下:
去重的一些细节:
因为不能对nums排序,所以之前用的指针去重在这里不能使用,只能用set在树层去重。而且如果发现新遍历的元素若小于path中最后一个元素,也可以停止遍历。
这道题是选取每个合适树节点的题。
代码
我的代码
class Solution:
def findSubsequences(self, nums: List[int]) -> List[List[int]]:
res = []
path = []
def helper(index):
used = set()
for i in range(index, len(nums)):
if nums[i] in used:
continue
used.add(nums[i])
if len(path) >= 1:
if nums[i] >= path[-1]:
path.append(nums[i])
res.append(path.copy())
helper(i+1)
path.pop()
else:
continue
else:
path.append(nums[i])
helper(i + 1)
path.pop()
helper(0)
return res
标准
class Solution:
def findSubsequences(self, nums):
result = []
path = []
self.backtracking(nums, 0, path, result)
return result
def backtracking(self, nums, startIndex, path, result):
if len(path) > 1:
result.append(path[:]) # 注意要使用切片将当前路径的副本加入结果集
# 注意这里不要加return,要取树上的节点
uset = set() # 使用集合对本层元素进行去重
for i in range(startIndex, len(nums)):
if (path and nums[i] < path[-1]) or nums[i] in uset:
continue
uset.add(nums[i]) # 记录这个元素在本层用过了,本层后面不能再用了
path.append(nums[i])
self.backtracking(nums, i + 1, path, result)
path.pop()
复杂度分析
- 时间复杂度:
O(n * 2^n)
- 空间复杂度:
O(n)
46.全排列
思路
具体的遍历树状图如下:
-
可以看出叶子节点,就是收割结果的地方。当收集元素的数组path的大小达到和nums数组一样大的时候,说明找到了一个全排列,也表示到达了叶子节点。
-
for循环里不需要startIndex,因为每次都需要从头开始搜索。
-
为了保证排列里一个元素只使用一次,可以使用像卡哥那样的处理方法。使用used数组,或者使用一个set(),加入元素和Pop元素的逻辑和path一样来记录用过的元素。
也可以使用slicing来将剩下的nums传入回溯的过程中。(我使用的是这种方法)
代码
slicing
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
res = []
path = []
def helper(numsList):
if len(path) == len(nums):
res.append(path.copy())
return
for i in range(len(numsList)):
path.append(numsList[i])
helper(numsList[:i]+numsList[i+1:])
path.pop()
helper(nums)
return res
使用set来记录用过的数组
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
res = []
seen = set()
def recur(array):
if len(array)==len(nums):
res.append(list(array))
else:
for num in nums:
if num not in seen:
seen.add(num)
array.append(num)
recur(array)
seen.remove(num)
array.pop()
recur([])
return res
复杂度分析
- 时间复杂度:
O(n!)
- 空间复杂度:
O(n)
47.全排列 II
思路
这道题涉及到除重,所以需要使用到used数组。因为排列的特殊性,使用指针,set()或者slicing都不可以。
使用[1,1,2]举例:
去重的细节:
如果要对树层中前一位去重,就用
used[i - 1] == false
,如果要对树枝前一位去重用used[i - 1] == true
。
[1,1,1] 来举一个例子
树层上去重(used[i - 1] == false),的树形结构如下:
树枝上去重(used[i - 1] == true)的树型结构如下:
代码
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
res = []
path = []
nums.sort()
used = [False] * len(nums)
def helper():
if len(path) == len(nums):
res.append(path.copy())
return
for i in range(len(nums)):
if i > 0 and nums[i] == nums[i-1] and used[i-1] == False:
continue
if used[i]:
continue
used[i] = True
path.append(nums[i])
helper()
path.pop()
used[i] = False
helper()
return res
复杂度分析
- 时间复杂度:
O(n! * n)
- 空间复杂度:
O(n)