跟随carl代码随想录刷题
语言:python
78. 子集
题目:给你一个整数数组 nums ,数组中的元素
互不相同
。返回该数组所有可能的子集(幂集)。
解集不能 包含重复的子集
。你可以按 任意顺序 返回解集。
👉示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
👉示例 2:
输入:nums = [0]
输出:[[],[0]]
题目分析
-
组合问题
和分割问题
都是收集树的叶子节点
-
子集问题
是找树的所有节点
-
本题终止条件可以不要,for循环结束后自动结束
if startIndex == len(nums):
return
-
注意:
res.append(path[:])
# 是path[:]
,而不是path
完整代码如下
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
res = []
path = []
def backtracking(nums, startIndex):
if path not in res:
res.append(path[:]) # 注意是path[:],而不是path
# 单层递归逻辑
for i in range(startIndex, len(nums)):
path.append(nums[i])
backtracking(nums, i+1)
path.pop()
backtracking(nums, 0)
return res
90. 子集 II
题目:给你一个整数数组 nums ,其中
可能包含重复元素
,请你返回该数组所有可能的子集(幂集)。
解集不能 包含重复的子集
。返回的解集中,子集可以按 任意顺序 排列。
👉示例 1:
输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]
👉示例 2:
输入:nums = [0]
输出:[[],[0]]
题目分析
因为数组中元素存在重复,因此在单层逻辑编写
的时候要进行树层去重
判断:如果当前元素与前一个元素重复了,那就终止这层逻辑,即:终止对当前元素的操作。接着对下一个元素进行判断。
因此要注意两点:
- 剪枝之前要提前排序
.sort()
- 树层去重:
if i > startIndex and nums[i] == nums[i-1]: continue
注:[:]
中,省略头部和尾部索引值,功能是复制列表
。
完整代码如下
class Solution:
def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
res = []
path = []
nums.sort() # 剪枝之前先排序
def backtracking(nums, startIndex):
if path not in res:
res.append(path[:]) # 这里一定要写path[:],而不是path。
# 终止条件
if startIndex == len(nums):
return
# 单层递归逻辑
for i in range(startIndex, len(nums)):
# 树层去重
if i < startIndex and nums[i]==nums[i-1]:
continue
path.append(nums[i])
backtracking(nums, i+1)
path.pop()
backtracking(nums, 0)
return res
491. 递增子序列
题目:给你一个整数数组 nums ,找出并返回所有该数组中
不同的递增子序列
,递增子序列中至少有两个元素
。你可以按 任意顺序 返回答案。
数组中可能含有重复元素
,如出现两个整数相等,也可以视作递增序列的一种特殊情况。
题目分析
- 递增子序列
-
本题要求递增子序列,所以
不能对数组排序
,否则就破坏了原数组的顺序。 -
⭐️本题有一个坑:只要求
递增子序列
,而不要求连续递增子序列
。- eg:如果
nums = [4, 7, 6, 7]
,那么[4, 7, 7]
是一个所求集合。 - 😄因此判断当前元素是否符合进入递增序列的条件,要看
它的值
与当前path中的最后一个元素值
的大小关系:if path and nums[i] < path[-1]: continue
- 😭错误写法:
if nums[i] < nums[i-1]: continue
# 如果当前元素比前一个元素小,那就结束对这个元素的操作。【这个比较是不对的,会导致直接在6
就continue了,导致集合[4, 7, 7]
,没办法统计】
- eg:如果
-
剪枝优化操作:
- 如果一个元素已经在前面的集合中作为头元素出现过了,那么对这个元素的操作就终止:
usage_list = set()
,if nums[i] in usage_list: continue
- eg:
nums = [4, 4, 5]
,以索引为0的元素4
为首部的集合有:[4, 4], [4, 4, 5], [4, 5]
。因此在遍历到 索引为1的元素4
时,直接continue
,因为此时以它为首部的集合[4, 5]
已经出现过了,没必要再统计。
- 如果一个元素已经在前面的集合中作为头元素出现过了,那么对这个元素的操作就终止:
-
- 至少有两个元素
- 所以:
if len(path) >= 2: res.append(path)
# 只会添加长度大于等于2的集合
- 所以:
- 因为一个元素不能重复使用,所以要使用
startIndex
作为每次回溯的起始点。 - 没有剪枝优化时,直接在进入结果集时加一个条件判断就好了:
if path not in res: res.append(path)
完整代码如下
回溯算法
class Solution:
def findSubsequences(self, nums: List[int]) -> List[List[int]]:
res = []
path = []
def backtracking(nums, startIndex):
if path not in res and len(path)>=2:
res.append(path[:])
# 单层回溯逻辑
for i in range(startIndex, len(nums)):
# 判断是否是递增的
if path and nums[i] < path[-1]:
continue
path.append(nums[i]) # 读取当前元素
backtracking(nums, i+1) # 回溯
path.pop()
backtracking(nums, 0)
return res
回溯算法——剪枝优化
增加一个对集合头部元素是否出现过的判断
class Solution:
def findSubsequences(self, nums: List[int]) -> List[List[int]]:
res = []
path = []
def backtracking(nums, startIndex):
if path not in res and len(path)>=2:
res.append(path[:])
# 单层回溯逻辑
usage_list = set()
for i in range(startIndex, len(nums)):
# 判断是否是递增的
if path and nums[i] < path[-1] or nums[i] in usage_list:
continue
usage_list.add(nums[i])
path.append(nums[i]) # 读取当前元素
backtracking(nums, i+1) # 回溯
path.pop()
backtracking(nums, 0)
return res