题目 491.递增子序列
问题描述
给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。
示例:
输入: [4, 6, 7, 7]
输出: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]
说明:
给定数组的长度不会超过15。
数组中的整数范围是 [-100,100]。
给定数组中可能包含重复数字,相等的数字应该被视为递增的一种情况。
解题思路
- 首先本体求的是递增的子集,是不能先把集合sort的,因为这样后子集就会变化。
- 画出本题的树形结构,
- 这道题中我们关心的是不能使用重复数值,只要使用过了就不行,所以按照元素的值设置used=[0]*201, 而不是组合总和2中,因为sort过后就要消除相邻位置的相同元素,当nums[i]=nums[i-1]而且nums[i]==0,就跳过。
- 从图中可以看到,去重有点类似上一题。对层去重,对树枝不去重。
一些代码问题
- continue
遇到某些条件时,加上continue就是跳过的意思,本体用来跳过递减等情况。 - hashset的索引
本题第二种方法采用hashmap去重- hash[0]*201,因为本题nums中的i取值范围为-100,100,一共201个数。
- 如果nums[i]=1,我们希望的是hash[101]=1。1在这个set当中的第101个,,就直接hash[nums[i]+100]=1。 同理nums[i]=44,那就hash[nums[i]+100]=1.
- 为什么回溯的时候不对set进行操作? 首先set的设置就是为了过滤同层相同元素,但是回溯相当于还在一个树枝,从下往上。
- 为什么终止条件那里不加return
if len(path) > 1: result.append(path[:]) 这段代码的目的是将长度大于1的递增子序列添加到 result 中。如果在这段代码后面加上 return,那么当找到一个长度大于1的递增子序列时,就会立即结束当前的函数调用,返回到上一层的函数调用。 - 回溯以后不对used对应值进行重设,因为只排出同层中的相同元素,不同层间used会被重设一次,本题的used定义在backtracking函数中。但是组合总和的used定义在主函数中,每一次回溯都是总体改变。
代码
- 回溯 利用set去重
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()
- 回溯 利用哈希表去重
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[:]) # 注意要使用切片将当前路径的副本加入结果集
used = [0] * 201 # 使用数组来进行去重操作,题目说数值范围[-100, 100]
for i in range(startIndex, len(nums)):
if (path and nums[i] < path[-1]) or used[nums[i] + 100] == 1:
continue # 如果当前元素小于上一个元素,或者已经使用过当前元素,则跳过当前元素
used[nums[i] + 100] = 1 # 标记当前元素已经使用过
path.append(nums[i]) # 将当前元素加入当前递增子序列
self.backtracking(nums, i + 1, path, result)
path.pop()
复杂度分析
题目 46.全排列 (回溯部分有点不懂)
问题描述
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
示例:
输入: [1,2,3]
输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]
解题思路
- 对于这个问题,我们需要找到数组中所有的排列,也就是长度等于原数组的子序列。
- 这些子序列必须包含原数组中的所有元素,且每个元素在子序列中只出现一次。
- 我们在每个节点选择一个还没有被选择过的数,然后进入下一层递归。在回溯的过程中,我们使用一个 used 数组来记录已经被选择的数。
- 和题目491的区别在:
- 每层都是从0开始搜索而不是startIndex
需要used数组记录path里都放了哪些元素了
- 每层都是从0开始搜索而不是startIndex
- 树形结构:
## 代码
- 用等于限制,在特定条件下要把回溯全部放在if里面。
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
used=[0]*len(nums)
result=[]
self.backtracking(nums,[],result,used)
return result
def backtracking(self,nums,path,result,used):
#设计一下,用used全为0的初始list且长度等于nums,将用过的数字对应的index加1
#end logic
if len(path)==len(nums):
result.append(path[:])
return
#single logic
for i in range(len(nums)):
if used[i]==1:
continue
path.append(nums[i])
used[i]=1
self.backtracking(nums,path,result,used)
#backtracking
path.pop()
used[i]=0
- 用不等于限制
if used[i]!=1:
path.append(nums[i])
used[i]=1
self.backtracking(nums,path,result,used)
#backtracking
path.pop()
used[i]=0
复杂度分析
-
时间复杂度O(n!)
- 每个元素在结果中都有 n!(n的阶乘)种可能的排列方式,所以总的时间复杂度为O(n!)。
-
空间复杂度O(n)
- 对于每一次递归调用,我们都会创建一个新的 path 变量(这个 path 是旧的 path 的复制并添加了一个元素)。当递归深度最大为n时,空间复杂度为O(n)。
题目 47.全排列 II
问题描述
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
示例 1:
输入:nums = [1,1,2]
输出: [[1,1,2], [1,2,1], [2,1,1]]
示例 2:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
提示:
1 <= nums.length <= 8
-10 <= nums[i] <= 10
解题思路
- 本题和全排列46相似,差别在于
- 本题nums中包含重复的数字
- 在去重部分先对nums进行排序,让一会儿了去同数字更加简便
- 注意在去重部分,nums【i】==nums【i-1】还要and两个条件:
- i是大于0的
- 前一个相同的数字而且还没被使用过·,即在同一层,如果是同一列被用过那就不用去重。
代码
注意是前一个元素为0的意思是前一个元素刚被回溯上来所以used等于0!!因为前一个元素等于1的话是在同一支。
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
nums.sort() # 排序
result=[]
hashset=[0]*len(nums)
self.backtracking(nums,[],result,hashset)
return result
def backtracking(self,nums,path,result,hashset):
#end logic
if len(path)==len(nums):
result.append(path[:])
return
#single logic
for i in range(len(nums)):
if hashset[i]!=0:
continue
if i>0 and nums[i]==nums[i-1] and hashset[i-1]==0:
continue
path.append(nums[i])
hashset[i]=1
self.backtracking(nums,path,result,hashset)
#backtracking
path.pop()
hashset[i]=0