题目链接:491. 递增子序列 - 力扣(Leetcode)
乍一看和子集问题很像,但自己写还真不容易写出来,没有用used记录当前一层遍历已经使用过的元素时,结果集合会出现重复,也不能直接套用子集问题的去重方式。
class Solution:
def __init__(self):
self.result = []
self.path = []
def backtracking(self, nums, startIndex, n):
if len(self.path) >= 2:
self.result.append(self.path[:])
if startIndex == n:
return
# 需要一个集合记录本层已经使用过的元素
used = set()
for i in range(startIndex, n):
if (self.path and nums[i] < self.path[-1]) or nums[i] in used:
continue
used.add(nums[i])
self.path.append(nums[i])
self.backtracking(nums, i+1, n)
self.path.pop()
def findSubsequences(self, nums: List[int]) -> List[List[int]]:
self.backtracking(nums, 0, len(nums))
return self.result
题目链接:
排列问题和组合问题不同,每次递归的范围是除path中元素的所有元素,之前的做法是直接传入除去当前元素的列表切片,当传入递归函数的列表为空时,path已经构成数组的一种排列:
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
res, path = [], []
def dfs(li):
if not li:
res.append(path.copy()) # 添加path.copy()才不会在path结果在回溯被修改时影响res
return
for i in range(len(li)):
path.append(li[i])
dfs(li[:i] + li[i+1:]) # 不包含当前li[i]的列表继续递归
path.remove(li[i]) # 回溯
dfs(nums)
return res
讲解做法是使用used数组标记数组中哪些元素已经被使用,因为一个排列里一个元素只能使用一次(要和递增子序列问题中的used数组含义区分开来):
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
res, path = [], []
def dfs2(used):
if len(path) == len(nums):
res.append(path[:])
return
for i in range(len(nums)):
if used[i]:
continue
used[i] = 1
path.append(nums[i])
dfs2(used)
path.pop()
used[i] = 0
used = [0] * len(nums)
dfs2(used)
return res
题目链接:47. 全排列 II - 力扣(Leetcode)
相比于原始的全排列,含有重复元素时,同样需要像递增子序列的问题一样,对同一层遍历中已经用过的元素进行去重,为了避免和全排列中的used数组重名,这里使用levelused集合记录:
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
res, path = [], []
def dfs(nums, used):
if len(path) == len(nums):
res.append(path[:])
return
levelused = set()
for i in range(len(nums)):
if used[i] or nums[i] in levelused:
continue
used[i] = 1
levelused.add(nums[i])
path.append(nums[i])
dfs(nums, used)
path.pop()
used[i] = 0
used = [0] * len(nums)
dfs(nums, used)
return res
举例说明levelused的理解,比如数组[1, 1, 2, 3, 4],递归第一层中用第一个1作为排列的第一个元素,依次递归下去构成一些全排列的子集,这一层中levelused中添加1。当回溯回来,开始使用第二个1作为排列的第一个元素,递归下去构成的全排列子集会和前一组一模一样,因此需要通过去重去掉。
讲解中使用一个used数组同时完成去重的判断也很精妙!我当时就是没想清楚used[i-1]应该是true还是false,同时还想把used[i]也放到同一个if逻辑去判断,所以就放弃用used数组去重:
class Solution {
private:
vector> result;
vector path;
void backtracking (vector& nums, vector& used) {
// 此时说明找到了一组
if (path.size() == nums.size()) {
result.push_back(path);
return;
}
for (int i = 0; i < nums.size(); i++) {
// used[i - 1] == true,说明同一树枝nums[i - 1]使用过
// used[i - 1] == false,说明同一树层nums[i - 1]使用过
// 如果同一树层nums[i - 1]使用过则直接跳过
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
continue;
}
if (used[i] == false) {
used[i] = true;
path.push_back(nums[i]);
backtracking(nums, used);
path.pop_back();
used[i] = false;
}
}
}
public:
vector> permuteUnique(vector& nums) {
result.clear();
path.clear();
sort(nums.begin(), nums.end()); // 排序
vector used(nums.size(), false);
backtracking(nums, used);
return result;
}
};