代码随想录算法训练营第32天|回溯算法part05|491.递增子序列、46.全排列、47.全排列 II
491.递增子序列
自己做
思路:
集合问题
但区别于集合:
返回的子集必须是递增的,至少两个元素
且nums中可能含有重复的元素,这就意味着要进行去重,之前都是先排序,再用flag来去重,但是这个不能排序,排序就都是递增了,改变了原nums数组的顺序,所以只能用集合来辅助判断了。
在自己做题时就卡在了先排序的问题上了!!!
代码:
python
class Solution(object):
def __init__(self):
self.result = []
self.path = []
def findSubsequences(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
'''
要求:
1. 递增
2. 至少两个元素
3. 数组不能重复
情况:
1. 数组中可能有重复元素
思路:
1. 先对数组进行排序 (想法错误,不能排序)
2. 递归时要设置startIndex参数,以实现不从头开始递归
3. 设置result列表添加条件,至少两个元素才可以添加
'''
self.backtracking(nums, 0)
return self.result
def backtracking(self, nums, startIndex):
if len(self.path) >= 2:
self.result.append(list(self.path))
if startIndex == len(nums):
return
uset = set()
for index in range(startIndex, len(nums)):
# 不通过,出现在了这里,之前去重是先进行排序的在处理nums[index]和nums[index-1]是否相等,现在没排序(排序就都是递增了),不挨着了,该怎么处理呢?只能用集合了!
if nums[index] in uset:
continue
if len(self.path) > 0 and nums[index] < self.path[-1]:
continue
uset.add(nums[index])
flag[index] = 1
self.path.append(nums[index])
print("path = ", self.path)
self.backtracking(nums, index+1)
self.path.pop()
flag[index] = 0
return
代码随想录
思路:
而本题求自增子序列,是不能对原数组进行排序的,排完序的数组都是自增子序列了。
所以不能使用之前的去重逻辑!
本题的树形结构抽象为(相比之前的组合问题,本题同一层重复的元素不挨着了,因为不能排序):
回溯三部曲
- 递归函数参数
def backtracking(self, nums, startIndex):
- 终止条件
if len(self.path) >= 2: self.result.append(list(self.path))
- 单层递归逻辑和条件处理
在图中可以看出,同一父节点下的同层上使用过的元素就不能再使用了
uset = set()
for index in range(startIndex, len(nums)):
# 不通过,出现在了这里,之前去重是先进行排序的在处理nums[index]和nums[index-1]是否相等,现在没排序(排序就都是递增了),不挨着了,该怎么处理呢?只能用集合了!
if nums[index] in uset:
continue
if len(self.path) > 0 and nums[index] < self.path[-1]:
continue
uset.add(nums[index])
flag[index] = 1
self.path.append(nums[index])
print("path = ", self.path)
self.backtracking(nums, index+1)
self.path.pop()
flag[index] = 0
return
*是记录本层元素是否重复使用,新的一层uset都会重新定义(清空),所以要知道uset只负责本层!
代码:
python
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()
46.全排列
自己做
思路:
全排列,相比组合,就是强调元素的顺序的,只要循序不一致就可以
也就是说每次递归时都要考虑除自身之外其他元素,不再只是自身元素后面的元素,前面的也要考虑,每次传入数组时都要把除了自己的数组传入给下一次递归
代码:
python
使用python的切片
class Solution(object):
def __init__(self):
self.result = []
self.path = []
def permute(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
# 思路:
'''
1. 全排列,每次
'''
self.backtracking(nums)
return self.result
def backtracking(self, nums):
if len(nums) == 0:
self.result.append(list(self.path))
return
for index in range(len(nums)):
self.path.append(nums[index])
new_nums = nums[0:index] + nums[index+1:]
print(new_nums)
self.backtracking(new_nums)
self.path.pop()
return
代码随想录
思路:
以[1,2,3]为例,抽象成树形结构如下:
回溯三部曲
- 递归函数参数
首先排列是有序的,也就是说 [1,2] 和 [2,1] 是两个集合,这和之前分析的子集以及组合所不同的地方。
可以看出元素1在[1,2]中已经使用过了,但是在[2,1]中还要在使用一次1,所以处理排列问题就不用使用startIndex了。
但排列问题需要一个used数组,标记已经选择的元素,如图橘黄色部分所示:
代码:
def backtracking(self, nums, path, used, result):
- 终止条件
可以看出叶子节点,就是收割结果的地方。
if len(path) == len(nums):
result.append(path[:])
return
- 单层搜索的逻辑
这里和77.组合问题 (opens new window)、131.切割问题 (opens new window)和78.子集问题 (opens new window)最大的不同就是for循环里不用startIndex了。
因为排列问题,每次都要从头开始搜索,例如元素1在[1,2]中已经使用过了,但是在[2,1]中还要再使用一次1。
而used数组,其实就是记录此时path里都有哪些元素使用了,一个排列里一个元素只能使用一次。
for i in range(len(nums)):
if used[i]:
continue
used[i] = True
path.append(nums[i])
self.backtracking(nums, path, used, result)
path.pop()
used[i] = False
代码:
python
使用used数组标记
class Solution:
def permute(self, nums):
result = []
self.backtracking(nums, [], [False] * len(nums), result)
return result
def backtracking(self, nums, path, used, result):
if len(path) == len(nums):
result.append(path[:])
return
for i in range(len(nums)):
if used[i]:
continue
used[i] = True
path.append(nums[i])
self.backtracking(nums, path, used, result)
path.pop()
used[i] = False
47.全排列 II
自己做
思路:
相比不含重复数字的nums进行全排列,本题有重复数字,仅需在其基础上处理重复数字情况即可,使用set集合,如果数值在集合中出现过,说明在当前层处理过同样的数字
代码:
python
使用切片
class Solution(object):
def __init__(self):
self.result = []
self.path = []
def permuteUnique(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
# 思路:
# 相比不含重复数字的nums进行全排列,本题有重复数字,仅需在其基础上处理重复数字情况即可,使用set集合,如果数值在集合中出现过,说明在当前层处理过同样的数字
self.backtracking(nums)
return self.result
def backtracking(self, nums):
if len(nums) == 0:
self.result.append(list(self.path))
return
uset = set()
for index in range(len(nums)):
if nums[index] in uset:
continue
uset.add(nums[index])
self.path.append(nums[index])
new_nums = nums[0:index] + nums[index+1:]
print(new_nums)
self.backtracking(new_nums)
self.path.pop()
return
代码随想录
思路:
这道题目和46.全排列 (opens new window)的区别在与给定一个可包含重复数字的序列,要返回所有不重复的全排列。
又涉及到去重了
还要强调的是去重一定要对元素进行排序,这样我们才方便通过相邻的节点来判断是否重复使用了。(本题对元素排序也可以,使用集合判断是否出现过也可以)
我以示例中的 [1,1,2]为例 (为了方便举例,已经排序)抽象为一棵树,去重过程如图:
图中我们对同一树层,前一位(也就是nums[i-1])如果使用过,那么就进行去重。
一般来说:组合问题和排列问题是在树形结构的叶子节点上收集结果,而子集问题就是取树上所有节点的结果。
代码:
python
class Solution:
def permuteUnique(self, nums):
nums.sort() # 排序
result = []
self.backtracking(nums, [], [False] * len(nums), result)
return result
def backtracking(self, nums, path, used, result):
if len(path) == len(nums):
result.append(path[:])
return
for i in range(len(nums)):
if (i > 0 and nums[i] == nums[i - 1] and not used[i - 1]) or used[i]:
continue
used[i] = True
path.append(nums[i])
self.backtracking(nums, path, used, result)
path.pop()
used[i] = False