491. 非递减子序列
本题并不能像 90.子集II 那样,使用排序进行树层去重。虽然题目没有明确不能排序,但如果排序了,集合本身就是递增子序列,这是LeetCode示例2中没有出现的。
所以本题的关键在于,如何在不排序的情况下对树层去重。在树层遍历的时候(也就是for循环),我们维护一个 used 数组,记录已经使用过的元素。
class Solution:
def findSubsequences(self, nums: List[int]) -> List[List[int]]:
def backtrack(startIndex, path):
used = set()
for i in range(startIndex, len(nums)):
# 如果元素小于path中的最后一个元素,无法构成递增
# 如果元素已经在同一树层被使用过,则需要去重
if (path and nums[i] < path[-1]) or nums[i] in used:
continue
used.add(nums[i])
path.append(nums[i])
# 收集符合条件的节点
if len(path) > 1:
result.append(path[:])
backtrack(i + 1, path)
path.pop()
result = []
backtrack(startIndex = 0, path = [])
return result
46.全排列
排列问题和组合问题的区别在于,顺序不同算作不同排列。在组合问题中,我们定义 startIndex 确保每次递归只能向后探索,否则可能出现 [1, 2] 和 [2, 1] 这种重复组合。在排列问题中,我们不再需要 startIndex,只需确保同一元素不被重复选取。
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
def backtrack(path):
# 全排列在叶子结点处收集结果
if len(path) == len(nums):
result.append(path[:])
return
for i in range(len(nums)):
# 只要同一元素不被重复选取
# 顺序不同算作不同排列
if nums[i] in path:
continue
path.append(nums[i])
backtrack(path)
path.pop()
result = []
backtrack(path = [])
return result
47.全排列II
本题多了一个存在重复数字的条件,因此为了确保同一元素不被重复选取,采用上一题的方法并不行。因为我们只是不能取同一元素,但如果两个元素的值相同呢?我们需要借助used数组 通过位置来判断而不是值。
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
def backtrack(path, used):
if len(path) == len(nums):
result.append(path[:])
return
for i in range(len(nums)):
# 避免同一元素被多次使用
if used[i]:
continue
# 树层去重,由于 nums 包含重复数字,结果需要去重
if i > 0 and nums[i] == nums[i-1] and used[i-1] == False:
continue
used[i] = True
path.append(nums[i])
backtrack(path, used)
path.pop()
used[i] = False
result = []
nums.sort()
backtrack(path = [], used = [False] * len(nums))
return result